argparse子命令错误信息

7
考虑以下Python 2代码:
from argparse import ArgumentParser

p = ArgumentParser(prog="test")
p.add_argument('--bar')
sp = p.add_subparsers()
sp1 = sp.add_parser('foo')
sp1.add_argument('--baz')

p.parse_args(['foo', '--bar'])

我收到的错误信息是:
usage: test [-h] [--bar BAR] {foo} ...
test: error: unrecognized arguments: --bar

这似乎暗示--bartest命令的未识别参数。但实际上,它是foo子命令的未识别参数。

我认为错误信息应该是:

usage: test foo [-h] [--baz BAZ]
foo: error: unrecognized arguments: --bar

这是argparse的一个bug吗?我能否配置argparse以获得正确的错误信息?

3个回答

4
如果我修改您的脚本
p = ArgumentParser(prog="test")
p.add_argument('--bar')
sp = p.add_subparsers(dest='cmd')
sp1 = sp.add_parser('foo')
sp1.add_argument('--baz')
print p.parse_known_args()

输出结果为:
1517:~/mypy$ python2.7 stack25333847.py foo --bar
(Namespace(bar=None, baz=None, cmd='foo'), ['--bar'])

解析器 p 遇到了其中一种允许的 sp 选择之一,即 foo。因此,它现在将解析委托给子解析器 sp1sp1 不认识 --bar,所以它将其作为未被识别的参数列表返回到主解析器中。默认操作是使主解析器将其传递出去,就好像它自己没有识别出该字符串一样。

由于它位于 foo 之后,--bar 也不被任何一个解析器所识别。 ['foo','--boo'] 的情况也是如此。

委托给子解析器是通过 sp(子解析器动作)的 __call__ 方法完成的。 它的一部分内容如下:

def __call__(self, parser, namespace, values, option_string=None):
    ...
    # parse all the remaining options into the namespace
    # store any unrecognized options on the object, so that the top
    # level parser can decide what to do with them
    namespace, arg_strings = parser.parse_known_args(arg_strings, namespace)
    if arg_strings:
        vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, [])
        getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)

因为设计上未识别参数的处理留给了主解析器(调用parse_args而不是parse_known_args的解析器)。


如果遇到其他错误,比如省略了--baz的值,则会在子解析器中生成错误消息:

1523:~/mypy$ python2.7 stack25333847.py foo --baz
usage: test foo [-h] [--baz BAZ]
test foo: error: argument --baz: expected one argument

我已经想出了一种生成的方法:

usage: test foo [-h] [--baz BAZ]
test foo: error: unrecognized arguments: --bar

虽然这并不是简短明了的,但我会子类化argparse._SubParsersAction,为它提供一个新的__call__方法,使用parse_args而不是parse_known_args。此外,我还需要更改主解析器注册表。(如需代码,请告知)。


很棒的答案。如果您有机会打印子解析器特定的错误消息,添加代码将是不错的选择。 - joemooney

1
我不会说这是一个 bug,而更倾向于认为这只是一种简单的错误信息生成方式。
默认的错误信息只是简单地声明:" {PROG}:错误:无法识别参数:{ALL_UNRECOGNIZED_ARGS}",而不管未被识别的参数与哪个(子)解析器相关联。
如果你看一下 parse_args() 如何生成错误信息, 就会发现这些信息并不容易获得。
def parse_args(self, args=None, namespace=None):
    args, argv = self.parse_known_args(args, namespace)
    if argv:
        msg = _('unrecognized arguments: %s')
        self.error(msg % ' '.join(argv))
    return args

调用 args, argv = parse_known_args(args) 将简单地解析已知的参数并将它们作为命名空间args返回,并将所有剩余的未知参数作为argv返回。

因此,您可以直接使用parse_known_args()。您还可以使用以下方式将子命令名称存储到命名空间中

sp = p.add_subparsers(dest='cmd')

然后使用args.cmd访问它,以确定用户尝试调用哪个子命令。根据这个,根据您的解析器层次结构的复杂程度,您可以生成更有帮助的错误消息(参见parser.error())。但它不会完美无缺。如果用户为主解析器和子解析器指定了未知的参数,则这两个未识别的参数将保留在argv中,我看不出任何明显的方法来确定它们中的哪一个被给予命令行上的哪个命令。

0

我已经通过以下方式解决了问题:

from argparse import ArgumentParser, _

class _ArgumentParser(ArgumentParser):
    # "parse_known_args" is made to behave exactly like "parse_args".
    def parse_known_args(self, args=None, namespace=None):
        args, argv = super().parse_known_args(args, namespace)
        if argv:
            msg = _('unrecognized arguments: %s')
            self.error(msg % ' '.join(argv))
        return args, argv

p = ArgumentParser(prog="test")
p.add_argument('--bar')
sp = p.add_subparsers(parser_class=_ArgumentParser)
sp1 = sp.add_parser('foo')
sp1.add_argument('--baz')

p.parse_args(['foo', '--bar'])

我收到以下错误信息:
usage: test foo [-h] [--baz BAZ]
test foo: error: unrecognized arguments: --bar

我认为这正是所需的,不是吗?

我不知道为什么Python有parse_known_args。我完全看不出它的用处,我认为无论如何都应该使用parse_args


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接