ArgumentParser:子Parser不继承已注册的类型

2024-06-16 11:29:23 发布

您现在位置:Python中文网/ 问答频道 /正文

我们可以将自己的类型添加到解析器的注册表中,例如:

from argparse import ArgumentParser
from distutils.util import strtobool

parser = ArgumentParser('flats')
parser.register('type', 'boolean', strtobool)
parser.add_argument('--conveyor-belt', type='boolean')

到目前为止,这是可行的,即args = parser.parse_args(['--conveyor-belt', 'on'])然后args.conveyor_belt == True。但在尝试添加子Parser时:

subparsers = parser.add_subparsers()
subparser = subparsers.add_parser('slaughterhouse')
subparser.add_argument('--rotating-knives', type='boolean')  # crash

我们在这里得到错误:ValueError 'boolean' is not callable。你知道吗

我希望所有子parser自动继承父级已注册的类型。可能吗?你知道吗


Tags: fromimportaddparser类型typeargsargument
1条回答
网友
1楼 · 发布于 2024-06-16 11:29:23

几年前,我在这里演示了register的用法。你知道吗

Parsing boolean values with argparse

它不是一个隐藏的特性(即没有“\”),但也没有文档记录。你知道吗

每个解析器都有一个registry,它将字符串与类和函数相匹配。如果action='store_true',解析器在registry中查找“store\u true”,并找到相应的Action子类。你知道吗

print(parser._registries)

type字符串也会被查找,不过默认情况下只注册“None”。其他常见的类型值,如intfloat,是Python函数,不需要注册。你知道吗

子解析器是使用主解析器类的新解析器。我不认为它们使用它的任何__init__参数,当然它们也不使用任何在创建之后可能已经更改的属性。你知道吗

可以定义一个ArgumentParser子类,将这些项添加到注册表中。你知道吗

argparse.py代码的一些相关部分:

def add_subparsers(self, **kwargs):

        # add the parser class to the arguments if it's not present
        kwargs.setdefault('parser_class', type(self))
        ...
        # create the parsers action and add it to the positionals list
        parsers_class = self._pop_action_class(kwargs, 'parsers')
        action = parsers_class(option_strings=[], **kwargs)
        ....

class _SubParsersAction(Action):
    def __init__(self,
                 option_strings,
                 prog,
                 parser_class,
                 dest=SUPPRESS,
                 help=None,
                 metavar=None):
        ...
        self._parser_class = parser_class
    def add_parser(self, name, **kwargs):
       ...
        parser = self._parser_class(**kwargs)

因此,主解析器的类用subparsersAction对象记录,并且在创建子解析器时使用。但是像descriptionhelp_formatter这样的参数是从add_parser命令获取的,而不是继承的。有一个bug/问题要求继承formatter_class,但没有执行任何操作。你知道吗


从您的代码:

In [22]: parser._registries['type']
Out[22]: 
{None: <function argparse.ArgumentParser.__init__.<locals>.identity>,
 'boolean': <function distutils.util.strtobool>}

以及

In [24]: subparser._registries['type']
Out[24]: {None: <function argparse.ArgumentParser.__init__.<locals>.identity>}
In [25]: subparser._registries['type'].update(parser._registries['type'])
In [27]: subparser.add_argument(' rotating-knives', type='boolean')  # crash
Out[27]: _StoreAction(option_strings=[' rotating-knives'], dest='rotating_knives', nargs=None, const=None, default=None, type='boolean', choices=None, help=None, metavar=None)

或者不必update,只需共享字典:

subparser._registries = parser._registries

argparse做了很多属性共享。例如,_actions列表在parser和它的所有action_groups之间共享。你知道吗

因此,如果我要将它添加到argparse,或者我自己的版本中,我可能会修改_SubParsersAction类而不是ArgumentParser类。或者只是定义一个助手函数:

   def new_parser(subparsers, *args, **kwargs):
       # parser - take from global
       sp = subparsers.add_parser(*args, **kwargs)
       sp._registries = parser._registries
       sp.help.formatter_class = parser.formatter_class
       etc

据我所知,subparsers没有任何对主解析器的引用。也就是说,subparsersparser._actions列表中,但是没有其他方向的链接。你知道吗

更改子角色动作类

parser._defaults是另一个没有从主解析器传递到子解析器的属性。它是用set_defaults设置的,记录如下:

https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.set_defaults

在子Parser中设置不同的值作为将函数链接到子Parser的一种方式进行记录:

https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_subparsers

从这里关于subparser的问题来看,很明显这个特性非常有用。但它也有点棘手的使用,并不是每个人都喜欢。你知道吗

最初的作者Steven J.Bethard编写的最后一个补丁与在主解析器和子解析器之间传递Namespace有关。你知道吗

argparse set_defaults on subcommands should override top level set_defaults

但这不是每个人都喜欢的。你知道吗

argparse - subparsers does not retain namespace

根据当前的问题,我建议使用register来使用定制的_SubParsersAction,这很有趣。你知道吗

It uses a custom Action class (like your test case). It subclasses ._SubParsersAction, and replaces the 9351 namespace use with the original one. I use the registry to change the class that parser.add_subparsers() uses.

p.register('action', 'parsers', MyParserAction)

这是必需的,因为正如上面的add_subparsers引号所示,argparse使用注册表来确定要使用的子类。你知道吗

有人可能会说parser.register的这种用法需要记录在案。但我不知道如何才能做到这一点,而不会给不需要该功能的用户带来困惑。你知道吗

相关问题 更多 >