【问题标题】:argparse - Combining parent parser, subparsers and default valuesargparse - 结合父解析器、子解析器和默认值
【发布时间】:2014-08-31 05:13:33
【问题描述】:

我想在脚本中定义不同的子解析器,它们都从一个共同的父级继承选项,但具有不同的默认值。但是,它并没有按预期工作。

这就是我所做的:

import argparse

# this is the top level parser
parser = argparse.ArgumentParser(description='bla bla')

# this serves as a parent parser
base_parser = argparse.ArgumentParser(add_help=False)
base_parser.add_argument('-n', help='number', type=int)


# subparsers
subparsers = parser.add_subparsers()
subparser1= subparsers.add_parser('a', help='subparser 1', 
                                   parents=[base_parser])
subparser1.set_defaults(n=50)
subparser2 = subparsers.add_parser('b', help='subparser 2',
                                   parents=[base_parser])
subparser2.set_defaults(n=20)

args = parser.parse_args()
print args

当我从命令行运行脚本时,这是我得到的:

$ python subparse.py b
Namespace(n=20)

$ python subparse.py a
Namespace(n=20)

显然,第二个set_defaults 覆盖了父级中的第一个。由于 argparse 文档中没有任何关于它的内容(非常详细),我认为这可能是一个错误。

有一些简单的解决方案吗?之后我可以检查 args 变量并将 None 值替换为每个子解析器的预期默认值,但这正是我期望 argparse 为我做的。

顺便说一下,这是 Python 2.7。

【问题讨论】:

    标签: python argparse


    【解决方案1】:

    set_defaults 循环解析器的动作,并设置每个default 属性:

       def set_defaults(self, **kwargs):
            ...
            for action in self._actions:
                if action.dest in kwargs:
                    action.default = kwargs[action.dest]
    

    您的 -n 参数(action 对象)是在您定义 base_parser 时创建的。当使用parents 创建每个子解析器时,该操作将添加到每个子解析器的._actions 列表中。它没有定义新的动作;它只是复制指针。

    因此,当您在 subparser2 上使用 set_defaults 时,您会为此共享操作修改 default

    此操作可能是subparser1._action 列表中的第二项(h 是第一项)。

     subparser1._actions[1].dest  # 'n'
     subparser1._actions[1] is subparser2._actions[1]  # true
    

    如果第二条语句是 True,这意味着两个列表中的 action 相同。

    如果您为每个子解析器单独定义了-n,您将看不到这个。它们会有不同的动作对象。

    我根据我对代码的了解工作,而不是文档中的任何内容。最近在Cause Python's argparse to execute action for default 中指出,文档没有说明add_argument 返回Action 对象。这些对象是代码组织的重要组成部分,但在文档中并未引起太多关注。


    如果使用“解决”冲突处理程序,通过引用复制父操作也会产生问题,并且需要重用父操作。这个问题是在

    中提出的

    argparse conflict resolver for options in subcommands turns keyword argument into positional argument

    和 Python 错误问题:

    http://bugs.python.org/issue22401

    对于这个问题和那个问题,一个可能的解决方案是(可选地)复制操作,而不是共享参考。这样option_stringsdefaults 就可以在子级中修改而不影响父级。

    【讨论】:

    • 谢谢,我也很怀疑。我决定在解析参数后处理默认值。
    【解决方案2】:

    发生了什么

    这里的问题是解析器参数是对象,当解析器从它的父类继承时,它会将对父类操作的引用添加到它自己的列表中。当您调用 set_default 时,它会在该对象上设置默认值,该对象在子解析器之间共享。

    您可以检查子解析器来查看:

    >>> a1 = [ action for action in subparser1._actions if action.dest=='n' ].pop()
    >>> a2 = [ action for action in subparser2._actions if action.dest=='n' ].pop()
    >>> a1 is a2 # same object in memory
    True
    >>> a1.default
    20
    >>> type(a1)
    <class 'argparse._StoreAction'>
    

    第一个解决方案:显式将此参数添加到每个子解析器

    您可以通过将参数单独添加到每个子解析器而不是将其添加到基类来解决此问题。

    subparser1= subparsers.add_parser('a', help='subparser 1', 
                                   parents=[base_parser])
    subparser1.add_argument('-n', help='number', type=int, default=50)
    subparser2= subparsers.add_parser('b', help='subparser 2', 
                                   parents=[base_parser])
    subparser2.add_argument('-n', help='number', type=int, default=20)
    ...
    

    第二种解决方案:多个基类

    如果有许多子解析器共享相同的默认值,并且您想避免这种情况,您可以为每个默认值创建不同的基类。由于 parents 是基类的列表,您仍然可以将公共部分分组到另一个基类中,并传递给子解析器多个基类以继承。这可能是不必要的复杂。

    import argparse
    
    # this is the top level parser
    parser = argparse.ArgumentParser(description='bla bla')
    
    # this serves as a parent parser
    base_parser = argparse.ArgumentParser(add_help=False)
    # add common args
    
    # for group with 50 default
    base_parser_50 = argparse.ArgumentParser(add_help=False)
    base_parser_50.add_argument('-n', help='number', type=int, default=50)
    
    # for group with 50 default
    base_parser_20 = argparse.ArgumentParser(add_help=False)
    base_parser_20.add_argument('-n', help='number', type=int, default=20)
    
    # subparsers
    subparsers = parser.add_subparsers()
    subparser1= subparsers.add_parser('a', help='subparser 1', 
                                       parents=[base_parser, base_parser_50])
    
    subparser2 = subparsers.add_parser('b', help='subparser 2',
                                       parents=[base_parser, base_parser_20])
    
    args = parser.parse_args()
    print args
    

    第一个共享参数的解决方案

    您还可以为参数共享一个字典并使用解包来避免重复所有参数:

    import argparse
    
    # this is the top level parser
    parser = argparse.ArgumentParser(description='bla bla')
    
    n_args = '-n',
    n_kwargs = {'help': 'number', 'type': int}
    
    # subparsers
    subparsers = parser.add_subparsers()
    subparser1= subparsers.add_parser('a', help='subparser 1')
    subparser1.add_argument(*n_args, default=50, **n_kwargs)
    
    subparser2 = subparsers.add_parser('b', help='subparser 2')
    subparser2.add_argument(*n_args, default=20, **n_kwargs)
    
    args = parser.parse_args()
    print args
    

    【讨论】:

    • 感谢您的详尽回答。由于我有两个以上的子解析器和许多共享参数(包括多个父级),因此我决定创建一个函数来在解析参数后设置默认值。
    【解决方案3】:

    我希望多个子解析器也能继承公共参数,但 argparse 的 parents 功能也给我带来了问题,正如其他人所解释的那样。幸运的是,有一个非常简单的解决方案:创建一个函数来添加参数而不是创建父级。

    我将subparser1subparser2 都传递给一个函数parent_parser,它添加了公共参数-n

    import argparse
    
    # this is the top level parser
    parser = argparse.ArgumentParser(description='bla bla')
    
    # this serves as a parent parser
    def parent_parser(parser_to_update):
        parser_to_update.add_argument('-n', help='number', type=int)
        return parser_to_update
    
    
    # subparsers
    subparsers = parser.add_subparsers()
    subparser1 = subparsers.add_parser('a', help='subparser 1')
    subparser1 = parent_parser(subparser1)
    subparser1.set_defaults(n=50)
    subparser2 = subparsers.add_parser('b', help='subparser 2')
    subparser2 = parent_parser(subparser2)
    subparser2.set_defaults(n=20)
    
    args = parser.parse_args()
    print(args)
    

    当我运行脚本时:

    $ python subparse.py b
    Namespace(n=20)
    
    $ python subparse.py a
    Namespace(n=50)
    

    【讨论】:

      猜你喜欢
      • 2017-12-02
      • 2017-09-27
      • 2017-02-21
      • 2018-12-31
      • 1970-01-01
      • 2015-07-17
      • 2018-01-04
      • 2013-02-27
      • 2012-04-25
      相关资源
      最近更新 更多