在子解析器args之后添加顶级argparse参数

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?

Yon*_*han 7

实际上有一种方法可以做到。您可以使用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调用以使解析器运行其自己的错误路径。

这不是最优雅的解决方案,但我遇到了完全相同的问题,我在主解析器上的参数被用作可选标志,附加在子解析器中指定的内容之上。

但请记住以下几点:此代码将使一个人可以在运行中指定多个子解析器及​​其选项,这些参数调用的代码应该能够处理它。


hpa*_*ulj 5

一旦顶级解析器遇到“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,没有一种简单的方法可以解决这种行为。