如何在使用subparser的子命令后允许添加顶级程序参数?
我有一个包含多个子分段的程序,允许子命令,改变程序的行为。以下是其设置方式的示例:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import argparse
def task_a():
print('did task_a')
def task_c():
print('did task_c')
def task_d():
print('did task_d')
def run_foo(args):
a_arg = args.a
c_arg = args.c
if a_arg:
task_a()
if c_arg:
task_c()
def run_bar(args):
a_arg = args.a
d_arg = args.d
if a_arg:
task_a()
if d_arg:
task_d()
def parse():
'''
Run the program
arg parsing goes here, if program was run as a script
'''
# create the top-level parser
parser = argparse.ArgumentParser()
# add top-level args
parser.add_argument("-a", default = False, action = "store_true", dest = 'a')
# add subparsers
subparsers = parser.add_subparsers(title='subcommands', description='valid subcommands', help='additional help', dest='subparsers')
# create the parser for the "foo" command
parser_foo = subparsers.add_parser('foo')
parser_foo.set_defaults(func = run_foo)
parser_foo.add_argument("-c", default = False, action = "store_true", dest = 'c')
# create the parser for the "bar" downstream command
parser_bar = subparsers.add_parser('bar')
parser_bar.set_defaults(func = run_bar)
parser_bar.add_argument("-d", default = False, action = "store_true", dest = 'd')
# parse the args and run the default parser function
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
parse()
当我运行程序时,我可以使用它的args调用子命令:
$ ./subparser_order.py bar -d
did task_d
$ ./subparser_order.py foo -c
did task_c
但是如果我想从顶层包含args,我必须这样称呼它:
$ ./subparser_order.py -a foo -c
did task_a
did task_c
但是,我认为这很令人困惑,特别是如果有许多顶级args和许多子命令args;子命令foo
夹在中间,难以辨别。
我宁愿能够像subparser_order.py foo -c -a
那样调用该程序,但这不起作用:
$ ./subparser_order.py foo -c -a
usage: subparser_order.py [-h] [-a] {foo,bar} ...
subparser_order.py: error: unrecognized arguments: -a
事实上,在指定子命令后,您根本无法调用顶级参数:
$ ./subparser_order.py foo -a
usage: subparser_order.py [-h] [-a] {foo,bar} ...
subparser_order.py: error: unrecognized arguments: -a
是否有一个解决方案允许在子命令之后包含顶级args?
答案 0 :(得分:2)
顶级解析器遇到'foo'后,会将解析委托给parser_foo
。这会修改args
命名空间,然后返回。顶级解析器不会恢复解析。它只处理子分析器返回的任何错误。
In [143]: parser=argparse.ArgumentParser()
In [144]: parser.add_argument('-a', action='store_true');
In [145]: sp = parser.add_subparsers(dest='cmd')
In [146]: sp1 = sp.add_parser('foo')
In [147]: sp1.add_argument('-c', action='store_true');
In [148]: parser.parse_args('-a foo -c'.split())
Out[148]: Namespace(a=True, c=True, cmd='foo')
In [149]: parser.parse_args('foo -c'.split())
Out[149]: Namespace(a=False, c=True, cmd='foo')
In [150]: parser.parse_args('foo -c -a'.split())
usage: ipython3 [-h] [-a] {foo} ...
ipython3: error: unrecognized arguments: -a
你可以防止它在无法识别的参数上窒息,但它不会恢复解析:
In [151]: parser.parse_known_args('foo -c -a'.split())
Out[151]: (Namespace(a=False, c=True, cmd='foo'), ['-a'])
您还可以向subparser添加具有相同标志/ dest的参数。
In [153]: sp1.add_argument('-a', action='store_true')
In [154]: parser.parse_args('foo -c -a'.split())
Out[154]: Namespace(a=True, c=True, cmd='foo')
但是子条目的默认值会覆盖顶级值(有关此行为的错误/问题讨论)。
In [155]: parser.parse_args('-a foo -c'.split())
Out[155]: Namespace(a=False, c=True, cmd='foo')
可以使用两阶段解析器或自定义_SubParsersAction
类来解析该额外字符串。但是使用argparse
,这种行为并不容易。
答案 1 :(得分:1)
实际上有一种方法可以做到。您可以使用parse_known_args
,获取名称空间和未解析的参数,并将其传递回parse_args
调用。它将在第二遍中进行合并和覆盖,并且从那里遗留下来的任何参数仍将引发解析器错误。
简单的例子,这里是设置:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', action='store_true')
sp = parser.add_subparsers(dest='subargs')
sp_1 = sp.add_parser('foo')
sp_1.add_argument('-b', action='store_true')
print(parser.parse_args())
按照argparse正常工作的顺序:
- $ python3 argparse_multipass.py
Namespace(a=False, subargs=None)
- $ python3 argparse_multipass.py -a
Namespace(a=True, subargs=None)
- $ python3 argparse_multipass.py -a foo
Namespace(a=True, b=False, subargs='foo')
- $ python3 argparse_multipass.py foo
Namespace(a=False, b=False, subargs='foo')
- $ python3 argparse_multipass.py foo -b
Namespace(a=False, b=True, subargs='foo')
- $ python3 argparse_multipass.py -a foo -b
Namespace(a=True, b=True, subargs='foo')
现在,您无法在子解析器启动后解析参数:
- $ python3 argparse_multipass.py foo -b -a
usage: argparse_multipass.py [-h] [-a] {foo} ...
argparse_multipass.py: error: unrecognized arguments: -a
但是,您可以多次通过以使您的论点恢复原状。这是更新的代码:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', action='store_true')
sp = parser.add_subparsers(dest='subargs')
sp_1 = sp.add_parser('foo')
sp_1.add_argument('-b', action='store_true')
args = parser.parse_known_args()
print('Pass 1: ', args)
args = parser.parse_args(args[1], args[0])
print('Pass 2: ', args)
结果:
- $ python3 argparse_multipass.py
Pass 1: (Namespace(a=False, subargs=None), [])
Pass 2: Namespace(a=False, subargs=None)
- $ python3 argparse_multipass.py -a
Pass 1: (Namespace(a=True, subargs=None), [])
Pass 2: Namespace(a=True, subargs=None)
- $ python3 argparse_multipass.py -a foo
Pass 1: (Namespace(a=True, b=False, subargs='foo'), [])
Pass 2: Namespace(a=True, b=False, subargs='foo')
- $ python3 argparse_multipass.py foo
Pass 1: (Namespace(a=False, b=False, subargs='foo'), [])
Pass 2: Namespace(a=False, b=False, subargs='foo')
- $ python3 argparse_multipass.py foo -b
Pass 1: (Namespace(a=False, b=True, subargs='foo'), [])
Pass 2: Namespace(a=False, b=True, subargs='foo')
- $ python3 argparse_multipass.py -a foo -b
Pass 1: (Namespace(a=True, b=True, subargs='foo'), [])
Pass 2: Namespace(a=True, b=True, subargs='foo')
- $ python3 argparse_multipass.py foo -b -a
Pass 1: (Namespace(a=False, b=True, subargs='foo'), ['-a'])
Pass 2: Namespace(a=True, b=True, subargs='foo')
这将保留原始功能,但允许在子解析器启动时继续进行解析。此外,如果您执行以下操作,则可能会使整个解析过程变得混乱:
ns, ua = parser.parse_known_args()
while len(ua):
ns, ua = parser.parse_known_args(ua, ns)
它将一直解析参数,以防它们乱序,直到完成所有参数的解析为止。请记住,如果有一个未知的论据停留在那里,那么这一观点将继续下去。介意要添加这样的内容:
pua = None
ns, ua = parser.parse_known_args()
while len(ua) and ua != pua:
ns, ua = parser.parse_known_args(ua, ns)
pua = ua
ns, ua = parser.parse_args(ua, ns)
只需保留一个先前未解析的参数对象并进行比较,当它中断时,执行最后的parse_args
调用,以使解析器运行自己的错误路径。
这不是最优雅的解决方案,但我遇到了一个完全相同的问题,即在子解析器中指定的内容之上,我在主解析器上的参数还用作可选标志。
请记住以下几点:该代码将使人们可以在运行中指定多个子解析器及其选项,这些参数调用的代码应能够处理这些问题。