让顶级解析器和子解析器作用于同一个变量

问题描述 投票:0回答:1

我有一个带有子解析器的

ArgumentParser
。有些标志对于所有子解析器都是通用的,我希望能够在子命令之前或之后指定它们,或者甚至在之前和之后混合(由用户自行决定)。

类似这样的:

$ ./test -v Namespace(v=1) $ ./test.py test -vv Namespace(v=2) $ ./test.py -vvv test Namespace(v=3) $ ./test.py -vv test -vv Namespace(v=4)
所以我尝试了这样的事情:

#!/usr/bin/env python3 import argparse parser = argparse.ArgumentParser() parser.add_argument("-v", action="count") subparsers = parser.add_subparsers() sub = subparsers.add_parser("test") sub.add_argument("-v", action="count") print(parser.parse_args())
运行这个程序“test.py”给我:

./test.py -h usage: test.py [-h] [-v] {test} ... positional arguments: {test} options: -h, --help show this help message and exit -v $ ./test.py test -h usage: test.py test [-h] [-v] options: -h, --help show this help message and exit -v $ ./test.py -v Namespace(v=1) $ ./test.py test -vv Namespace(v=2)
酷。

但它也给了我:

$ ./test.py -vvv test Namespace(v=None) $ ./test.py -vv test -vv Namespace(v=2)
不太酷:-(

我还尝试显式指定父解析器:

common = argparse.ArgumentParser(add_help=False) common.add_argument("-v", action="count") parser = argparse.ArgumentParser(parents=[common]) sub = parser.add_subparsers().add_parser("test", parents=[common]) print(parser.parse_args())
但结果是一样的。

所以,我猜一旦

test

 子解析器启动,它就会将 
v
 的值重置为 
None

如何防止这种情况发生?

(我注意到

如何在 python argparse 中使用子解析器定义全局选项?是类似的,那里的答案建议为顶级和子级解析器使用不同的 dest

 变量。我会想避免这种情况...)

python argparse subparsers
1个回答
0
投票
使用

action='count'

(从 Python 3.12.3 开始)根本无法实现问题中 
ArgumentParser
 所需的行为,因为子命名空间最后如何合并到主命名空间中。  具体来说,内部 
_CountAction
 callable
会尝试在当前命名空间中查找
dest
,如果不存在则将其设置为 
0
,并将值增加 
1
。  然而,内部 
_SubparserAction
 callable
只会天真地合并结果值(不是确切的,但非常相关的
已知问题)。 您可以通过使用以下 argparser 跟踪执行来验证这一点(它设置了额外的默认标志以消除歧义以及示例输入),并单步执行调试器:

import argparse common = argparse.ArgumentParser(add_help=False) common.add_argument("-v", action="count") common.add_argument("-c", default="common") parser = argparse.ArgumentParser(parents=[common]) parser.add_argument("-p", default="parser") sub = parser.add_subparsers().add_parser("test", parents=[common]) sub.add_argument("-s", default="sub") args = ['-v', '-v', 'test', '-v', '-v'] print(f"final: {parser.parse_args(args)}")
除了单步执行调试器之外,在第二个代码块中的 

print(namespace)

 块之前插入 
_CountAction.__call__
print(f"{subnamespace} merging into {namespace}")
 的链接行上方,将产生以下输出:
for

我多年前在我编写的一个包中解决了这个特定问题(具体来说,
this commit
,它暗示了这个非常具体的问题),方法是使用通用解析器和

Namespace(v=None, c='common', p='parser') Namespace(v=1, c='common', p='parser') Namespace(v=None, c='common', s='sub') Namespace(v=1, c='common', s='sub') Namespace(v=2, c='common', s='sub') merging into Namespace(v=2, c='common', p='parser') final: Namespace(v=2, c='common', p='parser', s='sub') 以及传入的参数。 由于您的最后一个示例已经有一个通用解析器,因此将最后部分更改为:

parse_known_args

应产生以下输出:
print(f"common: {common.parse_known_args(args)}")
print(f"final: {parser.parse_args(args)}")

common: (Namespace(v=4, c='common'), ['test'])
final: Namespace(v=2, c='common', p='parser', s='sub')
的已知参数中选择您需要的内容(并忽略其余部分),并从

common

 中选择剩余部分。
	

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.