我所需要的
我需要一个带有冲突处理方案的ArgumentParser
,它解决一些已注册的重复参数集,但在所有其他参数上引发。
我试过的
我最初的方法(请参见下面的代码示例)是子类ArgumentParser
,添加一个_handle_conflict_custom
方法,然后用ArgumentParser(conflict_handler='custom')
实例化子类,认为_get_handler
方法会捡到它。
问题
这会引发一个错误,因为ArgumentParser
从提供_get_handler
和_handle_conflict_{strategy}
方法的_ActionsContainer
继承,然后在内部实例化一个_ArgumentGroup
(也是从_ActionsContainer
继承的),后者又不知道ArgumentParser
上新定义的方法,因此无法获得自定义处理程序。
出于同样的原因,重写_get_handler
方法是不可行的。
我创建了一个说明这些关系的(rudimentary)类图,因此希望子类ArgumentParser
以实现我想要的问题。
动机
我(认为,我)需要这一点,因为我有两个脚本,它们处理工作流中不同的部分,我希望能够单独使用它们作为脚本,但也有一个脚本,它导入这两个脚本的方法,并一次性完成所有操作。
这个脚本应该支持两个独立脚本的所有选项,但是我不想重复(广泛的)参数定义,因此我必须在多个地方进行更改。
通过导入(部件)脚本的ArgumentParsers
并将它们作为父脚本使用,这很容易解决,就像combined_parser = ArgumentParser(parents=[arg_parser1, arg_parser2])
一样。
在脚本中,我有重复的选项,例如工作目录,所以我需要解决这些冲突。
这也可以用conflict_handler='resolve'
来完成。
但是,由于存在大量可能的参数(这不取决于我们的团队,因为我们必须维护兼容性),所以我还希望脚本在定义了一些引起冲突但没有明确允许这样做的事情时引发错误,而不是悄悄地覆盖另一个标志,从而可能导致不必要的行为。
其他实现这些目标的建议(保持两个脚本分开,允许使用一个将两者封装在一起的脚本,避免代码重复和出现意外的重复)是受欢迎的。
示例代码
from argparse import ArgumentParser
class CustomParser(ArgumentParser):
def _handle_conflict_custom(self, action, conflicting_actions):
registered = ['-h', '--help', '-f']
conflicts = conflicting_actions[:]
use_error = False
while conflicts:
option_string, action = conflicts.pop()
if option_string in registered:
continue
else:
use_error = True
break
if use_error:
self._handle_conflict_error(action, conflicting_actions)
else:
self._handle_conflict_resolve(action, conflicting_actions)
if __name__ == '__main__':
ap1 = ArgumentParser()
ap2 = ArgumentParser()
ap1.add_argument('-f') # registered, so should be resolved
ap2.add_argument('-f')
ap1.add_argument('-g') # not registered, so should raise
ap2.add_argument('-g')
# this raises before ever resolving anything, for the stated reasons
ap3 = CustomParser(parents=[ap1, ap2], conflict_handler='custom')
其他问题
我知道类似的问题:
但是,尽管他们中的一些人对are解析用法和冲突提供了有趣的见解,但它们似乎解决了与我无关的问题。
发布于 2021-12-22 01:02:03
出于各种原因--尤其是测试的需要--我习惯于总是以数据结构的形式定义a解析配置,通常是一系列数据结构。ArgumentParser的实际创建是在一个可重用的函数中完成的,该函数简单地从dicts构建解析器。这种方法有许多好处,特别是对于更复杂的项目。
如果您的每个脚本都要转移到该模型,我认为您可能能够检测到该函数中的任何配置冲突并相应地提出,从而避免了从ArgumentParser继承和混乱地理解其内部结构的需要。
我不确定我能很好地理解你的冲突处理需求,所以下面的演示仅仅是寻找重复的选项,如果它看到了,就会提出,但是我认为你应该能够理解这个方法,并评估它是否适用于你的情况。的基本思想是在普通数据结构领域而不是在data解析的拜占庭世界中解决您的问题。
import sys
import argparse
from collections import Counter
OPTS_CONFIG1 = (
{
'names': 'path',
'metavar': 'PATH',
},
{
'names': '--nums',
'nargs': '+',
'type': int,
},
{
'names': '--dryrun',
'action': 'store_true',
},
)
OPTS_CONFIG2 = (
{
'names': '--foo',
'metavar': 'FOO',
},
{
'names': '--bar',
'metavar': 'BAR',
},
{
'names': '--dryrun',
'action': 'store_true',
},
)
def main(args):
ap = define_parser(OPTS_CONFIG1, OPTS_CONFIG2)
opts = ap.parse_args(args)
print(opts)
def define_parser(*configs):
# Validation: adjust as needed.
tally = Counter(
nm
for config in configs
for d in config
for nm in d['names'].split()
)
for k, n in tally.items():
if n > 1:
raise Exception(f'Duplicate argument configurations: {k}')
# Define and return parser.
ap = argparse.ArgumentParser()
for config in configs:
for d in config:
kws = dict(d)
xs = kws.pop('names').split()
ap.add_argument(*xs, **kws)
return ap
if __name__ == '__main__':
main(sys.argv[1:])
发布于 2022-04-07 13:11:27
虽然我同意FMc的方法在长期可行性方面可能是更好的方法,但我已经找到了一种将定制处理程序覆盖到ArgumentParser中的方法。
关键是重写_ActionsContainer类,它实际上定义了处理程序函数。然后重写ArgumentParser和_ArgumentGroup继承的基类。
在下面的例子中,我只添加了一个忽略任何冲突的处理程序,但是您可以添加任何您想要的自定义逻辑。
import argparse
class IgnorantActionsContainer(argparse._ActionsContainer):
def _handle_conflict_ignore(self, action, conflicting_actions):
pass
argparse.ArgumentParser.__bases__ = (argparse._AttributeHolder, IgnorantActionsContainer)
argparse._ArgumentGroup.__bases__ = (IgnorantActionsContainer,)
parser = argparse.ArgumentParser(conflict_handler="ignore")
parser.add_argument("-a", type=int, default=1)
parser.add_argument("-a", type=int, default=2)
parser.add_argument("-a", type=int, default=3)
parser.add_argument("-a", type=int, default=4)
print(parser.parse_args())
运行python custom_conflict_handler.py -h
打印:
usage: custom_conflict_handler.py [-h] [-a A] [-a A] [-a A] [-a A]
optional arguments:
-h, --help show this help message and exit
-a A
-a A
-a A
-a A
运行python custom_conflict_handler.py
打印:
Namespace(a=1)
运行python custom_conflict_handler.py -a 5
打印:
Namespace(a=5)
发布于 2021-12-22 21:48:27
基于FMcs方法,我创建了一些更详细的内容,我知道这不是代码评审,但反馈仍然是受欢迎的。还有,也许这能让人看到更多的东西。
import argparse
from collections import Counter, OrderedDict
from typing import List, Dict, Any
from copy import deepcopy
class OptionConf:
def __init__(self):
self._conf = OrderedDict() # type: Dict[str, List[Dict[str, Any]]]
self._allowed_dupes = list() # type: List[str]
def add_conf(self, command, *conf_args, **conf_kwargs):
if command not in self._conf:
self._conf[command] = []
conf_kwargs['*'] = conf_args
self._conf[command].append(conf_kwargs)
def add_argument(self, *conf_args, **conf_kwargs):
self.add_conf('add_argument', *conf_args, **conf_kwargs)
def register_allowed_duplicate(self, flag):
self._allowed_dupes.append(flag)
def generate_parser(self, **kwargs):
argument_parser = argparse.ArgumentParser(**kwargs)
for command, conf_kwargs_list in self._conf.items():
command_func = getattr(argument_parser, command)
for conf_kwargs in conf_kwargs_list:
list_args = conf_kwargs.pop('*', [])
command_func(*list_args, **conf_kwargs)
conf_kwargs['*'] = list_args
return argument_parser
def _get_add_argument_conf_args(self):
for command, kwargs_list in self._conf.items():
if command != 'add_argument':
continue
return kwargs_list
return []
def resolve_registered(self, other):
if self.__class__ == other.__class__:
conf_args_list = self._get_add_argument_conf_args() # type: List[Dict[str, Any]]
other_conf_args_list = other._get_add_argument_conf_args() # type: List[Dict[str, Any]]
# find all argument names of both parsers
all_names = []
for conf_args in conf_args_list:
all_names += conf_args.get('*', [])
all_other_names = []
for other_conf_args in other_conf_args_list:
all_other_names += other_conf_args.get('*', [])
# check for dupes and throw if appropriate
found_allowed_dupes = []
tally = Counter(all_names + all_other_names)
for name, count in tally.items():
if count > 1 and name not in self._allowed_dupes:
raise Exception(f'Duplicate argument configurations: {name}')
elif count > 1:
found_allowed_dupes.append(name)
# merge them in a new OptionConf, preferring the args of self (AS OPPOSED TO ORIGINAL RESOLVE)
new_opt_conf = OptionConf()
for command, kwargs_list in self._conf.items():
for kwargs in kwargs_list:
list_args = kwargs.get('*', [])
new_opt_conf.add_conf(command, *list_args, **kwargs)
for command, kwargs_list in other._conf.items():
for kwargs in kwargs_list:
# if it's another argument, we remove dupe names
if command == 'add_argument':
all_names = kwargs.pop('*', [])
names = [name for name in all_names if name not in found_allowed_dupes]
# and only add if there are names left
if names:
new_opt_conf.add_argument(*deepcopy(names), **deepcopy(kwargs))
# put names back
kwargs['*'] = all_names
else:
# if not, we just add it
list_args = kwargs.pop('*', [])
new_opt_conf.add_conf(command, *deepcopy(list_args), **deepcopy(kwargs))
# put list args back
kwargs['*'] = list_args
return new_opt_conf
raise NotImplementedError()
if __name__ == '__main__':
opts_conf = OptionConf()
opts_conf.add_argument('pos_arg')
opts_conf.add_argument('-n', '--number', metavar='N', type=int)
opts_conf.add_argument('-i', '--index')
opts_conf.add_argument('-v', '--verbose', action='store_true')
opts_conf2 = OptionConf()
opts_conf2.add_argument('-n', '--number', metavar='N', type=int)
opts_conf2.add_argument('-v', action='store_true')
opts_conf.register_allowed_duplicate('-n')
opts_conf.register_allowed_duplicate('--number')
try:
resolved_opts = opts_conf.resolve_registered(opts_conf2)
except Exception as e:
print(e) # raises on -v
opts_conf.register_allowed_duplicate('-v')
resolved_opts = opts_conf.resolve_registered(opts_conf2)
ap = resolved_opts.generate_parser(description='does it work?')
ap.parse_args(['-h'])
https://stackoverflow.com/questions/70442764
复制相似问题