如何使用python argparseparsing多个子命令?

我正在实施一个命令行程序,其界面如下所示:

cmd [GLOBAL_OPTIONS] {command [COMMAND_OPTS]} [{command [COMMAND_OPTS]} ...] 

我已经通过了argparse文档 。 我可以使用argparse add_argument实现GLOBAL_OPTIONS作为可选参数。 和{command [COMMAND_OPTS]}使用子命令 。

从文档看来,我只能有一个子命令。 但正如你所看到的,我必须实现一个或多个子命令。 parsing使用argparse命令行参数的最佳方法是什么?

@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 

现在首先parsing所有的链接命令存储在extra 。 我重新parsing它,而不是空的获取所有链接的命令,并为他们创build单独的命名空间。 而我得到更好的用法stringargparse生成。

我想出了同样的问题,似乎我有一个更好的答案。

解决的办法是我们不能简单地将子语言与子语言分区嵌套,但是我们可以在语法分析器之后添加子语言分析器。

代码告诉你如何:

 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() 

parse_known_args返回一个名字空间和一个未知string列表。 这与选中的答案中的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') [] 

一个可选的循环会给每个子分析器自己的命名空间。 这允许在定位名称中重叠。

 argslist = [] while rest: args,rest = parser.parse_known_args(rest) argslist.append(args) 

你总是可以自己分割命令行(在你的命令名上分割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... 

未经testing

你可以尝试arghandler 。 这是对子命令的显式支持的扩展。

通过@mgilson改进了答案,我写了一个小的parsing方法,将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) 

它的行为正确,提供了很好的帮助:

对于./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 

并创build包含参数值的命名空间的层次结构:

 ./test.py cmd1 --foo 3 cmd3 --foo 4 Namespace(cmd1=Namespace(foo='3'), cmd2=None, cmd3=Namespace(foo='4')) 

你可以使用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