Argparse 与所需的子解析器

问题描述 投票:0回答:3

我正在使用 Python 3.4,我尝试将

argparse
与子解析器一起使用,并且我希望与 Python 2.x 中的行为类似,如果我不提供位置参数(以指示子解析器/子程序)我会收到一条有用的错误消息。即,使用
python2
我会收到以下错误消息:

$ python2 subparser_test.py    
usage: subparser_test.py [-h] {foo} ...
subparser_test.py: error: too few arguments

我按照

https://stackoverflow.com/a/22994500/3061818
中的建议设置required属性,但这给了我Python 3.4.0的错误:
TypeError: sequence item 0: expected str instance, NoneType found
- 完整回溯:

$ python3 subparser_test.py    
Traceback (most recent call last):
  File "subparser_test.py", line 17, in <module>
    args = parser.parse_args()
  File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 1717, in parse_args
    args, argv = self.parse_known_args(args, namespace)
  File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 1749, in parse_known_args
    namespace, args = self._parse_known_args(args, namespace)
  File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 1984, in _parse_known_args
    ', '.join(required_actions))
TypeError: sequence item 0: expected str instance, NoneType found

这是我的程序

subparser_test.py
- 改编自https://docs.python.org/3.2/library/argparse.html#sub-commands:

import argparse

# sub-command functions
def foo(args):
    print('"foo()" called')

# create the top-level parser
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
subparsers.required = True

# create the parser for the "foo" command
parser_foo = subparsers.add_parser('foo')
parser_foo.set_defaults(func=foo)

args = parser.parse_args()
args.func(args)

相关问题:为什么这个 argparse 代码在 Python 2 和 3 之间表现不同?

python python-3.x python-2.7 argparse
3个回答
163
投票

您需要给

subparsers
一个
dest

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='cmd')
subparsers.required = True

现在:

1909:~/mypy$ argdev/python3 stack23349349.py
usage: stack23349349.py [-h] {foo} ...
stack23349349.py: error: the following arguments are required: cmd

为了发出此“缺少参数”错误消息,代码需要为该参数指定一个名称。对于位置参数(如子解析),该名称(默认情况下)是“dest”。在您链接的 SO 答案中有一个关于此的(小)注释。

上一个 Python 版本中

argparse
的少数“补丁”之一改变了它测试“必需”参数的方式。不幸的是,它引入了有关子解析器的错误。这需要在下一个版本中修复(如果不是更早的话)。

更新

如果您想要 Py2 中的这种可选子解析器行为,看起来最好的选择是使用两阶段解析器,如

中所述

如何使用 Python 2.7 的 Argparse 模块设置默认子解析器

相关错误/问题最近有一些活动

https://bugs.python.org/issue9253

更新

正在修复此问题:https://github.com/python/cpython/pull/3027


0
投票

不再需要前面提到的解决方法,因为

add_mutually_exclusive_group()
方法也接受必需的参数。

这将表明至少需要一个互斥参数。

parser = argparse.ArgumentParser(prog='PROG')
group = parser.add_mutually_exclusive_group(required=True)

group.add_argument('--foo', action='store_true')
group.add_argument('--bar', action='store_false')

> parser.parse_args([])
> usage: PROG [-h] (--foo | --bar)
> PROG: error: one of the arguments --foo --bar is required

取自argsparse文档:https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_mutually_exclusive_group


0
投票

我想出了一个方便的填充层,它基本上将 Python 3.7 中添加的

parser.add_subparsers(required=True)
向后移植到 Python 3.6 或更早版本:

class ArgParseShim:
    def __init__(self):
        self.subparsers = []

    def add_required_subparsers(self, p):
        res = p.add_subparsers()
        self.subparsers.append(res)
        return res

    def epilogue(self):
        for sub in self.subparsers:
            keys = sub.choices.keys()
            assert len(keys) > 0, "required subparsers must have sub parser (mouthful it is!)"
            sub.metavar = '{' + ','.join(keys) + '}'
            sub.required = True

调用

shim.add_required_subparsers(p)
代替
p.add_subparsers(required=True)
,并在配置完所有命令后调用
shim.epilogue()
。用例:

if __name__ == '__main__':
    shim = ArgParseShim()
    parser = argparse.ArgumentParser()
    subs = shim.add_required_subparsers(parser)
    parser_warehouse = subs.add_parser("warehouse")
    parser_warehouse.add_argument("--config", "-c")
    warehouse_subs = shim.add_required_subparsers(parser_warehouse)
    create_warehouse = warehouse_subs.add_parser("create")
    drop_warehouse = warehouse_subs.add_parser("drop")
    shim.epilogue()
    parser.parse_args()

    ctl = Controller()
    create_warehouse.set_defaults(func=ctl.create_warehouse) # use set_defaults to link commands to workers as recommended in https://docs.python.org/3.6/library/argparse.html#:~:text=One%20particularly%20effective%20way
    drop_warehouse.set_defaults(func=ctl.drop_warehouse)
    parser.parse_args().func()
© www.soinside.com 2019 - 2024. All rights reserved.