argparse - 组合父解析器,子解析器和默认值

eri*_*krf 12 python argparse

我想在脚本中定义不同的子分析器,同时继承来自公共父级的选项,但具有不同的默认值.但它没有按预期工作.

这是我做的:

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
Run Code Online (Sandbox Code Playgroud)

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

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

$ python subparse.py a
Namespace(n=20)
Run Code Online (Sandbox Code Playgroud)

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

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

顺便说一下,这是Python 2.7.

hpa*_*ulj 7

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

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

你定义的时候创建了你的-n参数(一个action对象)base_parser.使用时创建每个子分析程序时parents,该操作将添加到._actions每个子分析程序的列表中.它没有定义新的行动; 它只是复制指针.

因此,当您使用set_defaultson时subparser2,您可以修改default此共享操作.

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

 subparser1._actions[1].dest  # 'n'
 subparser1._actions[1] is subparser2._actions[1]  # true
Run Code Online (Sandbox Code Playgroud)

如果第二个语句是True,则表示action两个列表中的相同.

如果您已-n为每个子分析器单独定义,则不会看到此内容.他们会有不同的动作对象.

我的工作是从我对代码的了解,而不是文档中的任何内容.最近在Cause Python的argparse中指出了默认执行操作,文档没有提到add_argument返回Action对象.这些对象是代码组织的重要组成部分,但它们在文档中并没有引起太多关注.


如果使用"resolve"冲突处理程序并且需要重用父级,则通过引用复制父操作也会产生问题.这个问题在

子命令中的选项的argparse冲突解析器将关键字参数转换为位置参数

和Python bug问题:

http://bugs.python.org/issue22401

对于这个问题而言,可能的解决方案是(可选地)制作动作的副本,而不是共享引用.这样option_strings,defaults可以在孩子身上修改和不影响父母.


Jon*_*den 5

发生了什么

这里的问题是解析器参数是对象,当解析器从它的父节点继承时,它会将父节点操作的引用添加到它自己的列表中.当您调用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'>
Run Code Online (Sandbox Code Playgroud)

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

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

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)
...
Run Code Online (Sandbox Code Playgroud)

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

如果有许多子分析程序共享相同的默认值,并且您希望避免这种情况,则可以为每个默认值创建不同的基类.由于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
Run Code Online (Sandbox Code Playgroud)

第一个解决方案是共享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
Run Code Online (Sandbox Code Playgroud)