在子解析器 args 之后添加顶级 argparse 参数 [英] Add top level argparse arguments after subparser args

查看:28
本文介绍了在子解析器 args 之后添加顶级 argparse 参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何允许在使用子解析器的子命令后添加顶级程序参数?

How can you allow for top-level program arguments to be added after using a subcommand from a subparser?

我有一个程序,其中包含多个子解析器以允许使用子命令,从而改变程序的行为.以下是其设置方式的示例:

I have a program that includes several subparsers to allow for subcommands, changing the behavior of the program. Here is an example of how its set up:

#!/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()

当我运行程序时,我可以像这样调用带有参数的子命令:

When I run the program I can call a subcommand with its args like this:

$ ./subparser_order.py bar -d
did task_d

$ ./subparser_order.py foo -c
did task_c

但是如果我想包含顶层的参数,我必须这样称呼它:

But if I want to include the args from the top level, I have to call it like this:

$ ./subparser_order.py -a foo -c
did task_a
did task_c

然而,我认为这很令人困惑,尤其是在有很多顶级参数和许多子命令参数的情况下;子命令 <​​code>foo 夹在中间,很难辨别.

However, I think this is confusing, especially if there are many top-level args and many subcommand args; the subcommand foo is sandwiched in the middle here and harder to discern.

我宁愿能够像 subparser_order.py foo -c -a 这样调用程序,但这不起作用:

I would rather be able to call the program like subparser_order.py foo -c -a, but this does not work:

$ ./subparser_order.py foo -c -a
usage: subparser_order.py [-h] [-a] {foo,bar} ...
subparser_order.py: error: unrecognized arguments: -a

实际上,在指定子命令后根本无法调用顶级参数:

In fact, you cannot call the top-level args at all after specifying a subcommand:

$ ./subparser_order.py foo -a
usage: subparser_order.py [-h] [-a] {foo,bar} ...
subparser_order.py: error: unrecognized arguments: -a

是否有允许在子命令之后包含顶级参数的解决方案?

Is there a solution that will allow for the top-level args to be included after the subcommand?

推荐答案

实际上有一种方法可以做到.您可以使用 parse_known_args,获取命名空间和未解析的参数,并将它们传递回 parse_args 调用.它将在第 2 次传递中组合和覆盖,从那以后任何剩余的参数仍将引发解析器错误.

There is actually a way to do it. You can use parse_known_args, take the namespace and unparsed arguments and pass these back to a parse_args call. It will combine and override in the 2nd pass and any left over arguments from there on will still throw parser errors.

简单的例子,这里是设置:

Simple example, here is the setup:

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 工作的正确顺序:

In the proper order for argparse to work:

- $ 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')

现在,您无法在子解析器启动后解析参数:

Now, you can't parse arguments after a subparser kicks in:

- $ python3 argparse_multipass.py foo -b -a
usage: argparse_multipass.py [-h] [-a] {foo} ...
argparse_multipass.py: error: unrecognized arguments: -a

但是,您可以多次通过以恢复您的论点.这是更新后的代码:

However, you can do a multi-pass to get your arguments back. Here is the updated code:

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

这将保持原始功能,但允许在子解析器启动时继续解析.此外,如果您执行以下操作,则可以完全无序解析:

This will maintain original functionality but allow continued parsing for when subparsers kick in. Additionally you could make disordered parsing out of the thing entirely if you do something like this:

ns, ua = parser.parse_known_args()
while len(ua):
    ns, ua = parser.parse_known_args(ua, ns)

它会继续解析参数,以防它们无序,直到它完成所有参数的解析.请记住,如果有一个未知的论点留在那里,这个论点将继续下去.头脑想添加这样的东西:

It will keep parsing arguments in case they are out of order until it has completed parsing all of them. Keep in mind this one will keep going if there is an unknown argument that stays in there. Mind want to add something like this:

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 调用以使解析器运行其自己的错误路径.

Just keep a previously unparsed arguments object and compare it, when it breaks do a final parse_args call to make the parser run its own errors path.

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

It's not the most elegant solution but I ran into the exact same problem where my arguments on the main parser were used as optional flags additionally on top of what was specified in a sub parser.

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

Keep the following in mind though: This code will make it so a person can specify multiple subparsers and their options in a run, the code that these arguments invoke should be able to deal with that.

这篇关于在子解析器 args 之后添加顶级 argparse 参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆