【问题标题】:Subparser argparse "error: too few arguments"子解析器 argparse“错误:参数太少”
【发布时间】:2019-04-09 00:52:06
【问题描述】:
# bird.py
from argparse import ArgumentParser, SUPPRESS

parser = ArgumentParser(argument_default=SUPPRESS)
parser.add_argument('--dead', action='store_true')
subparsers = parser.add_subparsers()
subparser = subparsers.add_parser('parrot', parents=[parser], add_help=False)
subparser.add_argument('--volts', type=int)
args = parser.parse_args()
print(args)

上面的脚本bird.py 在 Python 3 上按预期工作。但在 Python 2.7 上它不会解析参数:

$ python3 bird.py parrot --volts 4000000
Namespace(volts=4000000)
$ python2 bird.py parrot --volts 4000000
usage: bird.py parrot [-h] [--dead] [--volts VOLTS] {parrot} ...
bird.py parrot: error: too few arguments

据我所知,代码中没有使用任何仅 py3 的功能。为什么不一样?如何更新此代码以便 CLI 也可以支持 Python 2.7,无需以任何方式修改其 Python 3 行为

【问题讨论】:

  • 最新的 Py3 允许我们在 add_subparsers 命令中指定 required=True,使其行为更像 Py2(但一定要设置 dest='cmd')。 Py3 有一个更丰富的缺失参数消息。在 Py2 中无法关闭所需的子解析器。
  • 是否'--dead parrot --volts 200' 在子解析器中实际设置dead=True'? I think the default=False` 会覆盖在主解析器中设置的任何值。从用户的角度来看,定义一个在主解析器和子解析器中都有效的参数可能很好,但以可靠的方式实现却很棘手。
  • 嗯,由于您描述的原因,实际上它在此 MCVE 中不起作用。在实际情况下,我使用的是 argparse.SUPPRESS,我错误地认为这与这里无关 - 我已经更新了代码以制作更好的 MCVE。
  • 您可以尝试使用argparse.py 的本地副本。 py3 版本应该在 py2 中运行,只需进行一些更改(例如将 yield from 恢复为其原始形式)。

标签: python python-2.7 argparse


【解决方案1】:

parents 复制的数量超出了您的预期。 parents 试图复制父母的子解析器,最终试图使 subparser 成为它自己的子解析器。在 Python 3 上,子解析器默认是可选的,所以 parrot 没有得到自己的 parrot 并不是一个错误。在 Python 2 上,需要子解析器,所以 parrot 需要它自己的 parrot 并抱怨没有得到它。 (考虑到您在尝试将父解析器用作父解析器时通过添加子解析器来修改父解析器,如果损坏的不止于此,我不会感到惊讶。)

与其尝试将parser 用作其自己的子解析器的父级,不如创建一个单独的父级解析器并将其用作顶级解析器和鹦鹉子解析器的父级:

from argparse import ArgumentParser

shared = ArgumentParser(add_help=False)
shared.add_argument('--dead', action='store_true')

parser = ArgumentParser(parents=[shared])
subparsers = parser.add_subparsers()
subparser = subparsers.add_parser('parrot', parents=[shared])
subparser.add_argument('--volts', type=int)
args = parser.parse_args()
print(args)

结果:

$ python2 bird.py parrot --volts 4000000
Namespace(dead=False, volts=4000000)
$ python3.6 bird.py parrot --volts 4000000
Namespace(dead=False, volts=4000000)

但是,还有更多问题,因为您正在向父母和孩子添加--dead 选项,并且他们正在尝试写入同一个地方。孩子似乎优先:

$ python bird.py --dead parrot
Namespace(dead=False, volts=None)
$ python bird.py parrot --dead
Namespace(dead=True, volts=None)

如果顶级解析器和子解析器有不同的dest 值,或者如果他们有除store_true 以外的操作,那么给顶级解析器和子解析器一个--dead 选项可能是有意义的,但是您现在拥有的方式,没有多大意义。如果你想要不同的dest 值,我认为你不能为此使用parents 机制。

【讨论】:

    【解决方案2】:

    此错误来自subparsers.add_parser() 调用中的parents 参数。

    # bird.py
    from argparse import ArgumentParser
    parser = ArgumentParser()
    parser.add_argument('--dead', action='store_true')
    subparsers = parser.add_subparsers()
    subparser = subparsers.add_parser('parrot', add_help=False)
    subparser.add_argument('--volts', type=int)
    args = parser.parse_args()
    print(args)
    

    然后

    $ python scratch.py parrot --volts 1000
    Namespace(dead=False, volts=1000)
    

    原因是使用subparsers.add_parser()创建一个新的解析器调用调用ArgumentParser的构造函数:

    class ArgumentParser(_AttributeHolder, _ActionsContainer):
        """Object for parsing command line strings into Python objects.
    
        Keyword Arguments:
            - [...]
            - parents -- Parsers whose arguments should be copied into this one
        """
    

    调用subparsers.add_parser('parrot', parents=[parser], add_help=False) 会将根解析器参数复制到新的子解析器之一,包括子解析器本身。这会导致无法解析的无限解析循环。

    【讨论】:

    • parents 创建了一个递归子解析器。 python3 stack53158180.py parrot parrot parrot 有效。 Py2 也会递归,但仍然会遇到丢失的子解析器错误,只是更进一步。
    • @hpaulj 是的,我刚刚明白,在进行快速调试会话并查看 ArgumentParser 的 parents 参数的文档后
    • 移除父母是不可接受的,因为行为已被修改。查看与python3 bird.py parrot --dead --volts 4000000 的区别。
    • 只用dead 定义一个单独的parent 解析器,并将其与主解析器和子解析器一起使用。在其他情况下,使用“working-parser”作为父代一直存在问题。
    猜你喜欢
    • 1970-01-01
    • 2014-08-04
    • 2020-10-13
    • 2015-11-08
    • 2018-07-09
    • 2018-05-15
    • 2012-04-25
    • 1970-01-01
    • 2022-01-26
    相关资源
    最近更新 更多