Argparse:如果子解析器共享父级,则子解析器的选项会覆盖主解析器
Argparse: options for subparsers override main if both share parent
我将 argparse 与多个子解析器一起使用。我希望我的程序在 args 的任何地方都采用冗长选项,包括子解析器。
from argparse import ArgumentParser
p = ArgumentParser()
p.add_argument('--verbose', '-v', action='count')
sub = p.add_subparsers()
a = sub.add_parser('a')
print(p.parse_args())
默认情况下,主解析器的选项如果用于子解析器将抛出错误:
$ python tmp.py -v a
Namespace(verbose=1)
$ python tmp.py a -v
usage: tmp.py [-h] [--verbose] {a} ...
tmp.py: error: unrecognized arguments: -v
我研究了父解析器,来自 this answer。
from argparse import ArgumentParser
parent = ArgumentParser(add_help=False)
parent.add_argument('--verbose', '-v', action='count')
main = ArgumentParser(parents=[parent])
sub = main.add_subparsers()
a = sub.add_parser('a', parents=[parent])
print(main.parse_args())
虽然出于某种原因,none 的共享标志在主解析器上工作。
$ python tmp2.py a -vvv
Namespace(verbose=3)
$ python tmp2.py -vvv a
Namespace(verbose=None)
请注意,主解析器肯定有适当的参数,因为当我将其更改为 main = ArgumentParser()
时,我得到 error: unrecognized arguments: -v
。我在这里错过了什么?
首先,一些一般性评论。
主解析器处理输入直到调用子解析器,然后调用子解析器并给出剩余的 argv
。完成后,它的 namespace
被合并回主 namespace
.
parents
机制通过引用从 parent
复制操作。因此,您的主解析器和子解析器共享相同的 verbose
Action 对象。当子解析器尝试设置不同的默认值或帮助时,这是一个问题。这可能不是问题,但请记住。
即使没有 parents
机制,在主解析器和子解析器之间共享 dest
或选项标志也可能很棘手。是否应该使用子解析器 Action 的默认值?如果两者都用呢?子解析器是否覆盖主解析器的操作?
最初主要 namespace
被传递给子解析器,它修改并返回。这在不久前已更改(如果需要,我可以找到 bug/issue)。现在子解析器以默认的空 namespace
开始,填充它。然后将这些值合并到 main.
因此,在您链接的 SO 问题中,请注意较旧的答案。 argparse
可能从那以后发生了变化。
我认为您的情况是主解析器和子解析器 verbose
分别计数。当你得到 None
时,你看到的是子解析器的默认值。
_Count_Action
的 __call__
是:
def __call__(self, parser, namespace, values, option_string=None):
new_count = _ensure_value(namespace, self.dest, 0) + 1
setattr(namespace, self.dest, new_count)
我怀疑在较旧的 argparse
中共享命名空间时,count
会累积,但如果不重新创建较旧的样式 subparser
操作,我无法对其进行测试class.
https://bugs.python.org/issue15327 - 最初的开发者建议给两个不同的参数dest
。它记录了来自 main 和 sub 的输入。如果需要,您自己的代码可以合并结果。
https://bugs.python.org/issue27859argparse - subparsers does not retain namespace
。在这里,我建议一种重新创建旧样式的方法。
https://bugs.python.org/issue9351 argparse set_defaults on subcommands should override top level set_defaults
- 这是 2014 年更改命名空间使用的问题。
我对这种行为的解决方法(在@hpaulj 的回答中有很好的描述)是创建第二个解析器,它没有子解析器,只有第一次找到的位置参数。
第一个 parse_args,与第一个解析器一起使用,将验证位置参数和标志,在需要时显示错误消息或显示适当的帮助。
第二个 parse_args,对于第二个解析器,将正确填写命名空间。
以您的示例为基础:
from argparse import ArgumentParser
parent = ArgumentParser(add_help=False)
parent.add_argument('--verbose', '-v', action='count')
main1 = ArgumentParser(parents=[parent])
sub = main1.add_subparsers()
# eg: tmp.py -vv a -v
a = sub.add_parser('a', parents=[parent])
a.set_defaults(which='a')
# eg: tmp.py -vv v -v --output toto
b = sub.add_parser('b', parents=[parent])
b.add_argument('--output', type=str)
b.set_defaults(which='b')
args = main1.parse_args()
print(args)
# parse a second time with another parser
main2 = ArgumentParser(parents=[parent])
if args.which == 'a':
main2.add_argument('a')
elif args.which == 'b':
main2.add_argument('b')
main2.add_argument('--output', type=str)
print(main2.parse_args())
给出:
$ ./tmp.py -vv a -v
Namespace(verbose=1, which='a')
Namespace(a='a', verbose=3)
$ ./tmp.py -vv b -v --output toto
Namespace(output='toto', verbose=1, which='b')
Namespace(b='b', output='toto', verbose=3)
$ ./tmp.py -vv a --output
usage: tmp.py [-h] [--verbose] {a,b} ...
tmp.py: error: unrecognized arguments: --output
我将此技术用于多个嵌套子解析器。
我将 argparse 与多个子解析器一起使用。我希望我的程序在 args 的任何地方都采用冗长选项,包括子解析器。
from argparse import ArgumentParser
p = ArgumentParser()
p.add_argument('--verbose', '-v', action='count')
sub = p.add_subparsers()
a = sub.add_parser('a')
print(p.parse_args())
默认情况下,主解析器的选项如果用于子解析器将抛出错误:
$ python tmp.py -v a
Namespace(verbose=1)
$ python tmp.py a -v
usage: tmp.py [-h] [--verbose] {a} ...
tmp.py: error: unrecognized arguments: -v
我研究了父解析器,来自 this answer。
from argparse import ArgumentParser
parent = ArgumentParser(add_help=False)
parent.add_argument('--verbose', '-v', action='count')
main = ArgumentParser(parents=[parent])
sub = main.add_subparsers()
a = sub.add_parser('a', parents=[parent])
print(main.parse_args())
虽然出于某种原因,none 的共享标志在主解析器上工作。
$ python tmp2.py a -vvv
Namespace(verbose=3)
$ python tmp2.py -vvv a
Namespace(verbose=None)
请注意,主解析器肯定有适当的参数,因为当我将其更改为 main = ArgumentParser()
时,我得到 error: unrecognized arguments: -v
。我在这里错过了什么?
首先,一些一般性评论。
主解析器处理输入直到调用子解析器,然后调用子解析器并给出剩余的 argv
。完成后,它的 namespace
被合并回主 namespace
.
parents
机制通过引用从 parent
复制操作。因此,您的主解析器和子解析器共享相同的 verbose
Action 对象。当子解析器尝试设置不同的默认值或帮助时,这是一个问题。这可能不是问题,但请记住。
即使没有 parents
机制,在主解析器和子解析器之间共享 dest
或选项标志也可能很棘手。是否应该使用子解析器 Action 的默认值?如果两者都用呢?子解析器是否覆盖主解析器的操作?
最初主要 namespace
被传递给子解析器,它修改并返回。这在不久前已更改(如果需要,我可以找到 bug/issue)。现在子解析器以默认的空 namespace
开始,填充它。然后将这些值合并到 main.
因此,在您链接的 SO 问题中,请注意较旧的答案。 argparse
可能从那以后发生了变化。
我认为您的情况是主解析器和子解析器 verbose
分别计数。当你得到 None
时,你看到的是子解析器的默认值。
_Count_Action
的 __call__
是:
def __call__(self, parser, namespace, values, option_string=None):
new_count = _ensure_value(namespace, self.dest, 0) + 1
setattr(namespace, self.dest, new_count)
我怀疑在较旧的 argparse
中共享命名空间时,count
会累积,但如果不重新创建较旧的样式 subparser
操作,我无法对其进行测试class.
https://bugs.python.org/issue15327 - 最初的开发者建议给两个不同的参数dest
。它记录了来自 main 和 sub 的输入。如果需要,您自己的代码可以合并结果。
https://bugs.python.org/issue27859argparse - subparsers does not retain namespace
。在这里,我建议一种重新创建旧样式的方法。
https://bugs.python.org/issue9351 argparse set_defaults on subcommands should override top level set_defaults
- 这是 2014 年更改命名空间使用的问题。
我对这种行为的解决方法(在@hpaulj 的回答中有很好的描述)是创建第二个解析器,它没有子解析器,只有第一次找到的位置参数。
第一个 parse_args,与第一个解析器一起使用,将验证位置参数和标志,在需要时显示错误消息或显示适当的帮助。
第二个 parse_args,对于第二个解析器,将正确填写命名空间。
以您的示例为基础:
from argparse import ArgumentParser
parent = ArgumentParser(add_help=False)
parent.add_argument('--verbose', '-v', action='count')
main1 = ArgumentParser(parents=[parent])
sub = main1.add_subparsers()
# eg: tmp.py -vv a -v
a = sub.add_parser('a', parents=[parent])
a.set_defaults(which='a')
# eg: tmp.py -vv v -v --output toto
b = sub.add_parser('b', parents=[parent])
b.add_argument('--output', type=str)
b.set_defaults(which='b')
args = main1.parse_args()
print(args)
# parse a second time with another parser
main2 = ArgumentParser(parents=[parent])
if args.which == 'a':
main2.add_argument('a')
elif args.which == 'b':
main2.add_argument('b')
main2.add_argument('--output', type=str)
print(main2.parse_args())
给出:
$ ./tmp.py -vv a -v
Namespace(verbose=1, which='a')
Namespace(a='a', verbose=3)
$ ./tmp.py -vv b -v --output toto
Namespace(output='toto', verbose=1, which='b')
Namespace(b='b', output='toto', verbose=3)
$ ./tmp.py -vv a --output
usage: tmp.py [-h] [--verbose] {a,b} ...
tmp.py: error: unrecognized arguments: --output
我将此技术用于多个嵌套子解析器。