Vik*_*kas 66 python command-line-arguments argparse
我正在实现一个命令行程序,其界面如下:
cmd [GLOBAL_OPTIONS] {command [COMMAND_OPTS]} [{command [COMMAND_OPTS]} ...]
Run Code Online (Sandbox Code Playgroud)
我已经浏览了argparse文档.我可以GLOBAL_OPTIONS使用add_argumentin 实现可选参数argparse.并{command [COMMAND_OPTS]}使用子命令.
从文档中看来,我只能有一个子命令.但是你可以看到我必须实现一个或多个子命令.解析使用这些命令行参数的最佳方法是什么argparse?
Vik*_*kas 24
@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
Run Code Online (Sandbox Code Playgroud)
现在在第一次解析之后存储所有链式命令extra.我重新解析它,因为它不是空的,以获取所有链接的命令并为它们创建单独的命名空间.我得到了argparse生成的更好的用法字符串.
小智 16
我提出了相同的问题,似乎我得到了更好的答案.
解决方案是我们不能简单地将subparser与另一个subparser嵌套,但是我们可以使用解析器跟随另一个subparser添加subparser.
代码告诉你如何:
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()
Run Code Online (Sandbox Code Playgroud)
hpa*_*ulj 13
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
Run Code Online (Sandbox Code Playgroud)
生产:
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') []
Run Code Online (Sandbox Code Playgroud)
另一个循环会为每个subparser提供自己的命名空间.这允许位置名称重叠.
argslist = []
while rest:
args,rest = parser.parse_known_args(rest)
argslist.append(args)
Run Code Online (Sandbox Code Playgroud)
@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
Run Code Online (Sandbox Code Playgroud)
这使用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 的直接限制。这是一个简单的示例,显示了 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)
Run Code Online (Sandbox Code Playgroud)
这将提高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或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'])
Run Code Online (Sandbox Code Playgroud)
甚至可以在使用信息中列出不同的命令,请参阅我的回答/sf/answers/3499942981/
您始终可以自己拆分命令行(sys.argv根据您的命令名称拆分),然后仅将与特定命令对应的部分传递给parse_args-- 如果需要,您甚至可以使用Namespacenamespace 关键字来使用相同的内容。
使用以下命令对命令行进行分组很容易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...
Run Code Online (Sandbox Code Playgroud)
未经测试
改进@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)
Run Code Online (Sandbox Code Playgroud)
它表现得很好,提供了很好的 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}
Run Code Online (Sandbox Code Playgroud)
对于./test.py cmd1 --help:
usage: test.py cmd1 [-h] [--foo FOO]
optional arguments:
-h, --help show this help message and exit
--foo FOO
Run Code Online (Sandbox Code Playgroud)
并创建包含参数值的命名空间层次结构:
./test.py cmd1 --foo 3 cmd3 --foo 4
Namespace(cmd1=Namespace(foo='3'), cmd2=None, cmd3=Namespace(foo='4'))
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
44958 次 |
| 最近记录: |