use*_*531 6 python arguments argparse
在使用来自子解析器的子命令后,如何允许添加顶级程序参数?
我有一个包含多个子解析器的程序,允许使用子命令来更改程序的行为。这是如何设置的示例:
#!/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()
Run Code Online (Sandbox Code Playgroud)
当我运行程序时,可以使用其args调用子命令,如下所示:
$ ./subparser_order.py bar -d
did task_d
$ ./subparser_order.py foo -c
did task_c
Run Code Online (Sandbox Code Playgroud)
但是,如果我想从顶层包含args,则必须这样称呼它:
$ ./subparser_order.py -a foo -c
did task_a
did task_c
Run Code Online (Sandbox Code Playgroud)
但是,我认为这很令人困惑,尤其是在有许多顶级参数和许多子命令参数的情况下;子命令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
Run Code Online (Sandbox Code Playgroud)
实际上,在指定子命令后,您根本无法调用顶级args:
$ ./subparser_order.py foo -a
usage: subparser_order.py [-h] [-a] {foo,bar} ...
subparser_order.py: error: unrecognized arguments: -a
Run Code Online (Sandbox Code Playgroud)
是否有解决方案可以在子命令后包含顶级args?
实际上有一种方法可以做到。您可以使用parse_known_args, 获取命名空间和未解析的参数并将它们传递回parse_args调用。它将在第 2 次传递中组合和覆盖,并且从那里剩下的任何参数仍将引发解析器错误。
简单的例子,这里是设置:
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())
Run Code Online (Sandbox Code Playgroud)
按照 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')
Run Code Online (Sandbox Code Playgroud)
现在,您无法在子解析器启动后解析参数:
- $ python3 argparse_multipass.py foo -b -a
usage: argparse_multipass.py [-h] [-a] {foo} ...
argparse_multipass.py: error: unrecognized arguments: -a
Run Code Online (Sandbox Code Playgroud)
但是,您可以进行多次传递以恢复您的论点。这是更新后的代码:
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)
Run Code Online (Sandbox Code Playgroud)
以及它的结果:
- $ 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')
Run Code Online (Sandbox Code Playgroud)
这将保持原始功能,但允许在子解析器启动时继续解析。此外,如果您执行以下操作,则可以完全进行无序解析:
ns, ua = parser.parse_known_args()
while len(ua):
ns, ua = parser.parse_known_args(ua, ns)
Run Code Online (Sandbox Code Playgroud)
它将继续解析参数,以防它们无序,直到完成所有参数的解析。请记住,如果有一个未知的论点留在那里,这个论点会继续下去。头脑想添加这样的东西:
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)
Run Code Online (Sandbox Code Playgroud)
只需保留一个先前未解析的参数对象并进行比较,当它中断时执行最终parse_args调用以使解析器运行其自己的错误路径。
这不是最优雅的解决方案,但我遇到了完全相同的问题,我在主解析器上的参数被用作可选标志,附加在子解析器中指定的内容之上。
但请记住以下几点:此代码将使一个人可以在运行中指定多个子解析器及其选项,这些参数调用的代码应该能够处理它。
一旦顶级解析器遇到“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
Run Code Online (Sandbox Code Playgroud)
您可以防止它因无法识别的参数而阻塞,但它不会恢复解析:
In [151]: parser.parse_known_args('foo -c -a'.split())
Out[151]: (Namespace(a=False, c=True, cmd='foo'), ['-a'])
Run Code Online (Sandbox Code Playgroud)
您还可以向子解析器添加具有相同标志/目标的参数。
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')
Run Code Online (Sandbox Code Playgroud)
但子条目的默认值会覆盖顶级值(对此行为存在错误/问题讨论)。
In [155]: parser.parse_args('-a foo -c'.split())
Out[155]: Namespace(a=False, c=True, cmd='foo')
Run Code Online (Sandbox Code Playgroud)
可以使用两级解析器或自定义_SubParsersAction类来解析额外的字符串。但事实上argparse,没有一种简单的方法可以解决这种行为。