我实现它具有接口这样的命令行程序:
cmd [GLOBAL_OPTIONS] {command [COMMAND_OPTS]} [{command [COMMAND_OPTS]} ...]
我已经通过了argparse文档 。 我可以实现GLOBAL_OPTIONS
如使用可选的参数add_argument
在argparse
。 和{command [COMMAND_OPTS]}
使用子命令 。
从文档看来我只能有一个子命令。 但是,你可以看到我要实现一个或多个子命令。 什么是解析这样的命令行参数期运用最好的办法argparse
?
Answer 1:
@mgilson有一个很好的回答了这个问题。 但随着分裂问题sys.argv中我是,我失去所有的好的帮助信息Argparse为用户生成。 所以,我最后做的:
import argparse
## This function takes the 'extra' attribute from global namespace and re-parses it to create separate namespaces for all other chained commands.
def parse_extra (parser, namespace):
namespaces = []
extra = namespace.extra
while extra:
n = parser.parse_args(extra)
extra = n.extra
namespaces.append(n)
return namespaces
argparser=argparse.ArgumentParser()
subparsers = argparser.add_subparsers(help='sub-command help', dest='subparser_name')
parser_a = subparsers.add_parser('command_a', help = "command_a help")
## Setup options for parser_a
## Add nargs="*" for zero or more other commands
argparser.add_argument('extra', nargs = "*", help = 'Other commands')
## Do similar stuff for other sub-parsers
现在首先解析毕竟连锁命令都存储在extra
。 我重新分析它,而它不是空的让所有的连锁命令,并为他们创造不同的命名空间。 而我得到argparse产生更好的使用字符串。
Answer 2:
我想出了相同的qustion,似乎我有更好的回答。
解决的办法是,我们将不只是嵌套子分析器与另一子分析器,但我们可以用下面的另一个子分析器解析器下面添加子分析器。
守则告诉你如何:
parent_parser = argparse.ArgumentParser(add_help=False)
parent_parser.add_argument('--user', '-u',
default=getpass.getuser(),
help='username')
parent_parser.add_argument('--debug', default=False, required=False,
action='store_true', dest="debug", help='debug flag')
main_parser = argparse.ArgumentParser()
service_subparsers = main_parser.add_subparsers(title="service",
dest="service_command")
service_parser = service_subparsers.add_parser("first", help="first",
parents=[parent_parser])
action_subparser = service_parser.add_subparsers(title="action",
dest="action_command")
action_parser = action_subparser.add_parser("second", help="second",
parents=[parent_parser])
args = main_parser.parse_args()
Answer 3:
parse_known_args
返回一个命名空间和未知的字符串列表。 这是类似的extra
的检查答案。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--foo')
sub = parser.add_subparsers()
for i in range(1,4):
sp = sub.add_parser('cmd%i'%i)
sp.add_argument('--foo%i'%i) # optionals have to be distinct
rest = '--foo 0 cmd2 --foo2 2 cmd3 --foo3 3 cmd1 --foo1 1'.split() # or sys.argv
args = argparse.Namespace()
while rest:
args,rest = parser.parse_known_args(rest,namespace=args)
print args, rest
生产:
Namespace(foo='0', foo2='2') ['cmd3', '--foo3', '3', 'cmd1', '--foo1', '1']
Namespace(foo='0', foo2='2', foo3='3') ['cmd1', '--foo1', '1']
Namespace(foo='0', foo1='1', foo2='2', foo3='3') []
另一种循环会给每个子分析器自己的命名空间。 这允许positionals名称重叠。
argslist = []
while rest:
args,rest = parser.parse_known_args(rest)
argslist.append(args)
Answer 4:
你可以尝试arghandler 。 这是与子命令明确支持argparse延伸。
Answer 5:
您可以随时拆分命令行自己(分sys.argv
你的命令名称),然后只通过对应的特定命令的部分parse_args
-您甚至可以使用相同的Namespace
使用namespace关键字,如果你想。
分组命令行很容易与itertools.groupby
:
import sys
import itertools
import argparse
mycommands=['cmd1','cmd2','cmd3']
def groupargs(arg,currentarg=[None]):
if(arg in mycommands):currentarg[0]=arg
return currentarg[0]
commandlines=[list(args) for cmd,args in intertools.groupby(sys.argv,groupargs)]
#setup parser here...
parser=argparse.ArgumentParser()
#...
namespace=argparse.Namespace()
for cmdline in commandlines:
parser.parse_args(cmdline,namespace=namespace)
#Now do something with namespace...
未经测试
Answer 6:
提高对@mgilson答案,我写了分裂的argv成零件,并把命令的的参数值到命名空间的层次结构中的小解析方法:
import sys
import argparse
def parse_args(parser, commands):
# Divide argv by commands
split_argv = [[]]
for c in sys.argv[1:]:
if c in commands.choices:
split_argv.append([c])
else:
split_argv[-1].append(c)
# Initialize namespace
args = argparse.Namespace()
for c in commands.choices:
setattr(args, c, None)
# Parse each command
parser.parse_args(split_argv[0], namespace=args) # Without command
for argv in split_argv[1:]: # Commands
n = argparse.Namespace()
setattr(args, argv[0], n)
parser.parse_args(argv, namespace=n)
return args
parser = argparse.ArgumentParser()
commands = parser.add_subparsers(title='sub-commands')
cmd1_parser = commands.add_parser('cmd1')
cmd1_parser.add_argument('--foo')
cmd2_parser = commands.add_parser('cmd2')
cmd2_parser.add_argument('--foo')
cmd2_parser = commands.add_parser('cmd3')
cmd2_parser.add_argument('--foo')
args = parse_args(parser, commands)
print(args)
它的行为不当,提供了很好的帮助argparse:
对于./test.py --help
:
usage: test.py [-h] {cmd1,cmd2,cmd3} ...
optional arguments:
-h, --help show this help message and exit
sub-commands:
{cmd1,cmd2,cmd3}
对于./test.py cmd1 --help
:
usage: test.py cmd1 [-h] [--foo FOO]
optional arguments:
-h, --help show this help message and exit
--foo FOO
并创建包含参数值的命名空间的层次结构:
./test.py cmd1 --foo 3 cmd3 --foo 4
Namespace(cmd1=Namespace(foo='3'), cmd2=None, cmd3=Namespace(foo='4'))
Answer 7:
通过@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='?'
或nargs='+'
或nargs='*'
)。 - 任何已知的参数解析,而不在新的命令“破”。 例如在
PROG --foo command_b command_a --baz Z 12
与上面的代码, --baz Z
将由消耗command_b
,而不是由command_a
。
这些限制是argparse的直接限制。 下面是一个简单的例子使用单个subcommand-时,显示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()
不看arugments(如姓名command_a
或command_b
),而只是在那里发生的参数列表。 它还假定所有的子命令将消耗所有剩余的参数。 的这种限制argparse
还防止多命令subparsers的正确执行。 不幸的是这意味着,适当的实现需要的全部重写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
Answer 8:
支持并行解析器另一个包是“declarative_parser”。
import argparse
from declarative_parser import Parser, Argument
supported_formats = ['png', 'jpeg', 'gif']
class InputParser(Parser):
path = Argument(type=argparse.FileType('rb'), optional=False)
format = Argument(default='png', choices=supported_formats)
class OutputParser(Parser):
format = Argument(default='jpeg', choices=supported_formats)
class ImageConverter(Parser):
description = 'This app converts images'
verbose = Argument(action='store_true')
input = InputParser()
output = OutputParser()
parser = ImageConverter()
commands = '--verbose input image.jpeg --format jpeg output --format gif'.split()
namespace = parser.parse_args(commands)
和命名空间变为:
Namespace(
input=Namespace(format='jpeg', path=<_io.BufferedReader name='image.jpeg'>),
output=Namespace(format='gif'),
verbose=True
)
免责声明:我是作者。 需要Python 3.6。 要安装使用:
pip3 install declarative_parser
这里是文档 ,这里是在GitHub上回购 。
Answer 9:
您可以使用包optparse
import optparse
parser = optparse.OptionParser()
parser.add_option("-f", dest="filename", help="corpus filename")
parser.add_option("--alpha", dest="alpha", type="float", help="parameter alpha", default=0.5)
(options, args) = parser.parse_args()
fname = options.filename
alpha = options.alpha
文章来源: How to parse multiple nested sub-commands using python argparse?