@Vikas 提供的解决方案对于特定子命令的可选参数无效,但是该方法是有效的。这里是改进版:
import argparse
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')
parser_a = subparsers.add_parser('command_a', help='command_a help')
parser_a.add_argument('bar', type=int, help='bar help')
parser_b = subparsers.add_parser('command_b', help='command_b help')
parser_b.add_argument('--baz', choices='XYZ', help='baz help')
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
则会将它们作为元组的第二个返回值返回。在这种方法中,剩余的参数再次被提供给解析器。因此,对于每个命令,都会创建一个新的Namespace。
请注意,在这个基本示例中,所有全局选项都只添加到第一个选项Namespace中,而不是后续的Namespaces。
这种方法适用于大多数情况,但有三个重要限制:
- 不可能为不同的子命令使用相同的可选参数,比如
myprog.py command_a --foo=bar command_b --foo=bar
。
- 无法在子命令中使用任何可变长度的位置参数(
nargs='?'
或nargs='+'
或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')
parser_a = subparsers.add_parser('command_a', help='command_a help')
parser_a.add_argument('bar', type=int, help='bar help')
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()
过于贪心,假设可选参数spam
的值为command_a
。特别地,在“拆分”可选和位置参数时,_parse_known_args()
不查看参数的名称(如command_a
或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)
甚至可以在使用信息中列出不同的命令,详情请参见我的回答https://stackoverflow.com/a/49999185/428542
./setup.py
也有这种CLI界面风格,研究一下他们的源代码会很有趣。 - Ciro Santilli OurBigBook.com