@Vikas 提供的解决方案对于特定于子命令的可选参数失败,但该方法是有效的。这是一个改进的版本:
import argparse
# create the top-level parser
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('--foo', action='store_true', help='foo help')
subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name')
# create the parser for the "command_a" command
parser_a = subparsers.add_parser('command_a', help='command_a help')
parser_a.add_argument('bar', type=int, help='bar help')
# create the parser for the "command_b" command
parser_b = subparsers.add_parser('command_b', help='command_b help')
parser_b.add_argument('--baz', choices='XYZ', help='baz help')
# parse some argument lists
argv = ['--foo', 'command_a', '12', 'command_b', '--baz', 'Z']
while argv:
print(argv)
options, argv = parser.parse_known_args(argv)
print(options)
if not options.subparser_name:
break
这使用parse_known_args
而不是parse_args
. parse_args
一旦遇到当前子解析器未知的参数,就会中止,parse_known_args
将它们作为返回元组中的第二个值返回。在这种方法中,剩余的参数被再次提供给解析器。因此,对于每个命令,都会创建一个新的命名空间。
请注意,在这个基本示例中,所有全局选项仅添加到第一个选项命名空间,而不是后续命名空间。
这种方法适用于大多数情况,但有三个重要限制:
- 不能对不同的子命令使用相同的可选参数,例如
myprog.py command_a --foo=bar command_b --foo=bar
.
- 不能将任何可变长度的位置参数与子命令 (
nargs='?'
ornargs='+'
或nargs='*'
) 一起使用。
- 解析任何已知参数,而不会在新命令处“中断”。例如,
PROG --foo command_b command_a --baz Z 12
在上面的代码中,--baz Z
将被 消耗command_b
,而不是command_a
。
这些限制是 argparse 的直接限制。这是一个简单的示例,它显示了 argparse 的局限性——即使使用单个子命令——:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('spam', nargs='?')
subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name')
# create the parser for the "command_a" command
parser_a = subparsers.add_parser('command_a', help='command_a help')
parser_a.add_argument('bar', type=int, help='bar help')
# create the parser for the "command_b" command
parser_b = subparsers.add_parser('command_b', help='command_b help')
options = parser.parse_args('command_a 42'.split())
print(options)
这将提高error: argument subparser_name: invalid choice: '42' (choose from 'command_a', 'command_b')
.
原因是内部方法argparse.ArgParser._parse_known_args()
过于贪婪,并假定这command_a
是可选spam
参数的值。特别是,当“拆分”可选参数和位置参数时,_parse_known_args()
不查看参数的名称(如command_a
or command_b
),而只查看它们在参数列表中的出现位置。它还假定任何子命令都将使用所有剩余的参数。这种限制argparse
也妨碍了多命令子解析器的正确实现。不幸的是,这意味着正确的实现需要完全重写该argparse.ArgParser._parse_known_args()
方法,即 200 多行代码。
鉴于这些限制,可以选择简单地恢复为单个多选参数而不是子命令:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--bar', type=int, help='bar help')
parser.add_argument('commands', nargs='*', metavar='COMMAND',
choices=['command_a', 'command_b'])
options = parser.parse_args('--bar 2 command_a command_b'.split())
print(options)
#options = parser.parse_args(['--help'])
甚至可以在使用信息中列出不同的命令,见我的回答https://stackoverflow.com/a/49999185/428542