两个 Python argparse 对象可以合并吗?
Can two Python argparse objects be combined?
我有一个包含 parserA 的对象 A - 一个 argparse.ArgumentParser 对象
还有一个包含 parserB 的对象 B - 另一个 argparse.ArgumentParser
对象 A 包含对象 B 的一个实例,但是对象 B 的参数现在需要由对象 A 中的解析器解析(因为 A 是从命令行使用参数调用的对象,而不是 B)
有没有办法在Python对象A中写成:parserA += B.parserB?
您不能在另一个中使用一个 ArgumentParser
。但是有办法解决。您需要提取到向解析器添加参数的方法代码。
然后您将能够使用它们来合并解析器中的参数。
此外,对参数(与其解析器相关)进行分组也会更容易。但是你必须支持参数名称集不相交。
示例:
foo.py:
def add_foo_params( group ):
group.add_argument('--foo', help='foo help')
if __name__ = "__main__":
parser = argparse.ArgumentParser(prog='Foo')
boo.py
def add_boo_params( group ):
group.add_argument('--boo', help='boo help')
if __name__ = "__main__":
parser = argparse.ArgumentParser(prog='Boo')
fooboo.py
from foo import add_foo_params
from boo import add_boo_params
if __name__ = "__main__":
parser = argparse.ArgumentParser(prog='FooBoo')
foo_group = parser.add_argument_group(title="foo params")
boo_group = parser.add_argument_group(title="boo params")
add_foo_params( foo_group )
add_boo_params( boo_group )
argparse
是在 object 年代左右开发的。除了一些常量和实用函数外,它都是 class 定义。该文档侧重于使用而不是 class 结构。但这可能有助于理解其中的一点。
parser = argparse.ArgumentParser(...)
创建 parser
object.
arg1 = parser.add_argument(...)
创建一个 argparse.Action
(实际上是子 class)object 并将其添加到几个 parser
属性(列表)中。通常我们会忽略方法 return 这个 Action object 的事实,但偶尔我会发现它很有帮助。当我在交互式 shell 中构建解析器时,我看到了这个动作。
args = parser.parse_args()
运行另一个方法,并且 return 是一个命名空间 object (class argparse.Namespace
)。
组方法和子解析器方法还创建 return objects(组、操作 and/or 解析器)。
ArgumentParser
方法接受一个 parents
参数,其中值是解析器 object 的列表。
有
parsera = argparse.ArgumentParser(parents=[parserb])
在 parsera
的创建过程中,parserb
中的操作和组被复制到 parsera
。这样,parsera
将识别 parserb
所做的所有参数。我鼓励你测试一下。
但是有一些条件。副本是引用。也就是说,parsera
得到一个指向 parserb
中定义的每个 Action 的指针。偶尔会产生问题(我现在不谈)。一个或另一个必须有 add_help=False
。通常在创建时将帮助操作添加到解析器。但是,如果 parserb
也有帮助,则会出现必须解决的冲突(重复)。
但是如果 parsera
是独立于 parserb
创建的,则无法使用 parents
。没有用于从 parserb
添加操作的现有机制。有可能制作一个新的解析器,同时使用 parents
parserc = argparse.ArgumentParser(parents=[parsera, parserb])
我可能会编写一个函数,将参数从 parserb
添加到 parsera
,借鉴实现 parents
的方法的想法。但我必须知道如何解决冲突。
查看 argparse._ActionsContainer._add_container_actions
以了解参数(操作)如何从 parent
复制到 parser
。可能令人困惑的是,每个 Action 除了在 parser
.[=46 中之外,还是 group
(用户定义的或 2 个默认组之一(见帮助))的一部分=]
另一种可能是使用
[argsA, extrasA] = parserA.parse_known_args()
[argsB, extrasB] = parserB.parse_known_args() # uses the same sys.argv
# or
args = parserB.parse_args(extrasA, namespace=argsA)
有了这个,每个解析器都会处理它知道的参数,return 其余的在 extras
列表中。
除非解析器是为这种集成设计的,否则这种集成会有一些粗糙的边缘。使用 Arnial's
方法可能更容易处理这些冲突,即将共享参数定义放在您自己的方法中。其他人喜欢将参数参数放在某种数据库(列表、字典等)中,并从中构建解析器。您可以根据需要将解析器创建包装在尽可能多的样板层中。
对于您的用例,如果可以的话,您可以尝试通过专用方法在 classes 之间简单地共享相同的 argparse 对象。
以下是根据您的情况得出的。
import argparse
class B(object):
def __init__(self, parserB=argparse.ArgumentParser()):
super(B, self).__init__()
self.parserB = parserB
def addArguments(self):
self.parserB.add_argument("-tb", "--test-b", help="Test B", type=str, metavar="")
#Add more arguments specific to B
def parseArgs(self):
return self.parserB.parse_args()
class A(object):
def __init__(self, parserA=argparse.ArgumentParser(), b=B()):
super(A, self).__init__()
self.parserA = parserA
self.b = b
def addArguments(self):
self.parserA.add_argument("-ta", "--test-a", help="Test A", type=str, metavar="")
#Add more arguments specific to A
def parseArgs(self):
return self.parserA.parse_args()
def mergeArgs(self):
self.b.parserB = self.parserA
self.b.addArguments()
self.addArguments()
代码解释:
- 如前所述,在问题中,对象 A 和对象 B 包含它们自己的解析器对象。对象 A 还包含对象 B 的一个实例。
- 该代码只是将预期的流程分成单独的方法,这样就可以在尝试解析之前继续向单个解析器添加参数。
测试个人
a = A()
a.addArguments()
print(vars(a.parseArgs()))
# CLI Command
python test.py -ta "Testing A"
# CLI Result
{'test_a': 'Testing A'}
综合测试
aCombined = A()
aCombined.mergeArgs()
print(vars(aCombined.parseArgs()))
# CLI Command
testing -ta "Testing A" -tb "Testing B"
# CLI Result
{'test_b': 'Testing B', 'test_a': 'Testing A'}
额外
您还可以制作一个通用方法,该方法采用可变参数,并迭代并不断添加各种 classes 的参数。我使用通用的“解析器”属性名称为下面的示例创建了 class C 和 D。
多项测试
# Add method to Class A
def mergeMultiArgs(self, *objects):
parser = self.parserA
for object in objects:
object.parser = parser
object.addArguments()
self.addArguments()
aCombined = A()
aCombined.mergeMultiArgs(C(), D())
print(vars(aCombined.parseArgs()))
# CLI Command
testing -ta "Testing A" -tc "Testing C" -td "Testing D"
# CLI Result
{'test_d': 'Testing D', 'test_c': 'Testing C', 'test_a': 'Testing A'}
是的,它们可以合并,这样做:
这是一个合并两个参数的函数:
def merge_args_safe(args1: Namespace, args2: Namespace) -> Namespace:
"""
Merges two namespaces but throws an error if there are keys that collide.
ref:
:param args1:
:param args2:
:return:
"""
# - the merged args
# The vars() function returns the __dict__ attribute to values of the given object e.g {field:value}.
args = Namespace(**vars(args1), **vars(args2))
return args
测试
def merge_args_test():
args1 = Namespace(foo="foo", collided_key='from_args1')
args2 = Namespace(bar="bar", collided_key='from_args2')
args = merge_args(args1, args2)
print('-- merged args')
print(f'{args=}')
输出:
Traceback (most recent call last):
File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
pydev_imports.execfile(file, globals, locals) # execute the script
File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "/Users/brando/ultimate-utils/ultimate-utils-proj-src/uutils/__init__.py", line 1202, in <module>
merge_args_test()
File "/Users/brando/ultimate-utils/ultimate-utils-proj-src/uutils/__init__.py", line 1192, in merge_args_test
args = merge_args(args1, args2)
File "/Users/brando/ultimate-utils/ultimate-utils-proj-src/uutils/__init__.py", line 1116, in merge_args
args = Namespace(**vars(args1), **vars(args2))
TypeError: argparse.Namespace() got multiple values for keyword argument 'collided_key'
python-BaseException
你可以在这个图书馆找到它:https://github.com/brando90/ultimate-utils
如果您想解决冲突,请执行以下操作:
def merge_two_dicts(starting_dict: dict, updater_dict: dict) -> dict:
"""
Starts from base starting dict and then adds the remaining key values from updater replacing the values from
the first starting/base dict with the second updater dict.
For later: how does d = {**d1, **d2} replace collision?
:param starting_dict:
:param updater_dict:
:return:
"""
new_dict: dict = starting_dict.copy() # start with keys and values of starting_dict
new_dict.update(updater_dict) # modifies starting_dict with keys and values of updater_dict
return new_dict
def merge_args(args1: Namespace, args2: Namespace) -> Namespace:
"""
ref:
:param args1:
:param args2:
:return:
"""
# - the merged args
# The vars() function returns the __dict__ attribute to values of the given object e.g {field:value}.
merged_key_values_for_namespace: dict = merge_two_dicts(vars(args1), vars(args2))
args = Namespace(**merged_key_values_for_namespace)
return args
测试:
def merge_args_test():
args1 = Namespace(foo="foo", collided_key='from_args1')
args2 = Namespace(bar="bar", collided_key='from_args2')
args = merge_args(args1, args2)
print('-- merged args')
print(f'{args=}')
assert args.collided_key == 'from_args2', 'Error in merge dict, expected the second argument to be the one used' \
'to resolve collision'
我有一个包含 parserA 的对象 A - 一个 argparse.ArgumentParser 对象 还有一个包含 parserB 的对象 B - 另一个 argparse.ArgumentParser
对象 A 包含对象 B 的一个实例,但是对象 B 的参数现在需要由对象 A 中的解析器解析(因为 A 是从命令行使用参数调用的对象,而不是 B)
有没有办法在Python对象A中写成:parserA += B.parserB?
您不能在另一个中使用一个 ArgumentParser
。但是有办法解决。您需要提取到向解析器添加参数的方法代码。
然后您将能够使用它们来合并解析器中的参数。
此外,对参数(与其解析器相关)进行分组也会更容易。但是你必须支持参数名称集不相交。
示例:
foo.py:
def add_foo_params( group ):
group.add_argument('--foo', help='foo help')
if __name__ = "__main__":
parser = argparse.ArgumentParser(prog='Foo')
boo.py
def add_boo_params( group ):
group.add_argument('--boo', help='boo help')
if __name__ = "__main__":
parser = argparse.ArgumentParser(prog='Boo')
fooboo.py
from foo import add_foo_params
from boo import add_boo_params
if __name__ = "__main__":
parser = argparse.ArgumentParser(prog='FooBoo')
foo_group = parser.add_argument_group(title="foo params")
boo_group = parser.add_argument_group(title="boo params")
add_foo_params( foo_group )
add_boo_params( boo_group )
argparse
是在 object 年代左右开发的。除了一些常量和实用函数外,它都是 class 定义。该文档侧重于使用而不是 class 结构。但这可能有助于理解其中的一点。
parser = argparse.ArgumentParser(...)
创建 parser
object.
arg1 = parser.add_argument(...)
创建一个 argparse.Action
(实际上是子 class)object 并将其添加到几个 parser
属性(列表)中。通常我们会忽略方法 return 这个 Action object 的事实,但偶尔我会发现它很有帮助。当我在交互式 shell 中构建解析器时,我看到了这个动作。
args = parser.parse_args()
运行另一个方法,并且 return 是一个命名空间 object (class argparse.Namespace
)。
组方法和子解析器方法还创建 return objects(组、操作 and/or 解析器)。
ArgumentParser
方法接受一个 parents
参数,其中值是解析器 object 的列表。
有
parsera = argparse.ArgumentParser(parents=[parserb])
在 parsera
的创建过程中,parserb
中的操作和组被复制到 parsera
。这样,parsera
将识别 parserb
所做的所有参数。我鼓励你测试一下。
但是有一些条件。副本是引用。也就是说,parsera
得到一个指向 parserb
中定义的每个 Action 的指针。偶尔会产生问题(我现在不谈)。一个或另一个必须有 add_help=False
。通常在创建时将帮助操作添加到解析器。但是,如果 parserb
也有帮助,则会出现必须解决的冲突(重复)。
但是如果 parsera
是独立于 parserb
创建的,则无法使用 parents
。没有用于从 parserb
添加操作的现有机制。有可能制作一个新的解析器,同时使用 parents
parserc = argparse.ArgumentParser(parents=[parsera, parserb])
我可能会编写一个函数,将参数从 parserb
添加到 parsera
,借鉴实现 parents
的方法的想法。但我必须知道如何解决冲突。
查看 argparse._ActionsContainer._add_container_actions
以了解参数(操作)如何从 parent
复制到 parser
。可能令人困惑的是,每个 Action 除了在 parser
.[=46 中之外,还是 group
(用户定义的或 2 个默认组之一(见帮助))的一部分=]
另一种可能是使用
[argsA, extrasA] = parserA.parse_known_args()
[argsB, extrasB] = parserB.parse_known_args() # uses the same sys.argv
# or
args = parserB.parse_args(extrasA, namespace=argsA)
有了这个,每个解析器都会处理它知道的参数,return 其余的在 extras
列表中。
除非解析器是为这种集成设计的,否则这种集成会有一些粗糙的边缘。使用 Arnial's
方法可能更容易处理这些冲突,即将共享参数定义放在您自己的方法中。其他人喜欢将参数参数放在某种数据库(列表、字典等)中,并从中构建解析器。您可以根据需要将解析器创建包装在尽可能多的样板层中。
对于您的用例,如果可以的话,您可以尝试通过专用方法在 classes 之间简单地共享相同的 argparse 对象。 以下是根据您的情况得出的。
import argparse
class B(object):
def __init__(self, parserB=argparse.ArgumentParser()):
super(B, self).__init__()
self.parserB = parserB
def addArguments(self):
self.parserB.add_argument("-tb", "--test-b", help="Test B", type=str, metavar="")
#Add more arguments specific to B
def parseArgs(self):
return self.parserB.parse_args()
class A(object):
def __init__(self, parserA=argparse.ArgumentParser(), b=B()):
super(A, self).__init__()
self.parserA = parserA
self.b = b
def addArguments(self):
self.parserA.add_argument("-ta", "--test-a", help="Test A", type=str, metavar="")
#Add more arguments specific to A
def parseArgs(self):
return self.parserA.parse_args()
def mergeArgs(self):
self.b.parserB = self.parserA
self.b.addArguments()
self.addArguments()
代码解释:
- 如前所述,在问题中,对象 A 和对象 B 包含它们自己的解析器对象。对象 A 还包含对象 B 的一个实例。
- 该代码只是将预期的流程分成单独的方法,这样就可以在尝试解析之前继续向单个解析器添加参数。
测试个人
a = A()
a.addArguments()
print(vars(a.parseArgs()))
# CLI Command
python test.py -ta "Testing A"
# CLI Result
{'test_a': 'Testing A'}
综合测试
aCombined = A()
aCombined.mergeArgs()
print(vars(aCombined.parseArgs()))
# CLI Command
testing -ta "Testing A" -tb "Testing B"
# CLI Result
{'test_b': 'Testing B', 'test_a': 'Testing A'}
额外
您还可以制作一个通用方法,该方法采用可变参数,并迭代并不断添加各种 classes 的参数。我使用通用的“解析器”属性名称为下面的示例创建了 class C 和 D。
多项测试
# Add method to Class A
def mergeMultiArgs(self, *objects):
parser = self.parserA
for object in objects:
object.parser = parser
object.addArguments()
self.addArguments()
aCombined = A()
aCombined.mergeMultiArgs(C(), D())
print(vars(aCombined.parseArgs()))
# CLI Command
testing -ta "Testing A" -tc "Testing C" -td "Testing D"
# CLI Result
{'test_d': 'Testing D', 'test_c': 'Testing C', 'test_a': 'Testing A'}
是的,它们可以合并,这样做:
这是一个合并两个参数的函数:
def merge_args_safe(args1: Namespace, args2: Namespace) -> Namespace:
"""
Merges two namespaces but throws an error if there are keys that collide.
ref:
:param args1:
:param args2:
:return:
"""
# - the merged args
# The vars() function returns the __dict__ attribute to values of the given object e.g {field:value}.
args = Namespace(**vars(args1), **vars(args2))
return args
测试
def merge_args_test():
args1 = Namespace(foo="foo", collided_key='from_args1')
args2 = Namespace(bar="bar", collided_key='from_args2')
args = merge_args(args1, args2)
print('-- merged args')
print(f'{args=}')
输出:
Traceback (most recent call last):
File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
pydev_imports.execfile(file, globals, locals) # execute the script
File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "/Users/brando/ultimate-utils/ultimate-utils-proj-src/uutils/__init__.py", line 1202, in <module>
merge_args_test()
File "/Users/brando/ultimate-utils/ultimate-utils-proj-src/uutils/__init__.py", line 1192, in merge_args_test
args = merge_args(args1, args2)
File "/Users/brando/ultimate-utils/ultimate-utils-proj-src/uutils/__init__.py", line 1116, in merge_args
args = Namespace(**vars(args1), **vars(args2))
TypeError: argparse.Namespace() got multiple values for keyword argument 'collided_key'
python-BaseException
你可以在这个图书馆找到它:https://github.com/brando90/ultimate-utils
如果您想解决冲突,请执行以下操作:
def merge_two_dicts(starting_dict: dict, updater_dict: dict) -> dict:
"""
Starts from base starting dict and then adds the remaining key values from updater replacing the values from
the first starting/base dict with the second updater dict.
For later: how does d = {**d1, **d2} replace collision?
:param starting_dict:
:param updater_dict:
:return:
"""
new_dict: dict = starting_dict.copy() # start with keys and values of starting_dict
new_dict.update(updater_dict) # modifies starting_dict with keys and values of updater_dict
return new_dict
def merge_args(args1: Namespace, args2: Namespace) -> Namespace:
"""
ref:
:param args1:
:param args2:
:return:
"""
# - the merged args
# The vars() function returns the __dict__ attribute to values of the given object e.g {field:value}.
merged_key_values_for_namespace: dict = merge_two_dicts(vars(args1), vars(args2))
args = Namespace(**merged_key_values_for_namespace)
return args
测试:
def merge_args_test():
args1 = Namespace(foo="foo", collided_key='from_args1')
args2 = Namespace(bar="bar", collided_key='from_args2')
args = merge_args(args1, args2)
print('-- merged args')
print(f'{args=}')
assert args.collided_key == 'from_args2', 'Error in merge dict, expected the second argument to be the one used' \
'to resolve collision'