首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >ArgumentParser的自定义冲突处理

ArgumentParser的自定义冲突处理
EN

Stack Overflow用户
提问于 2021-12-21 23:43:52
回答 3查看 304关注 0票数 6

我所需要的

我需要一个带有冲突处理方案的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'来完成。

但是,由于存在大量可能的参数(这不取决于我们的团队,因为我们必须维护兼容性),所以我还希望脚本在定义了一些引起冲突但没有明确允许这样做的事情时引发错误,而不是悄悄地覆盖另一个标志,从而可能导致不必要的行为。

其他实现这些目标的建议(保持两个脚本分开,允许使用一个将两者封装在一起的脚本,避免代码重复和出现意外的重复)是受欢迎的。

示例代码

代码语言:javascript
运行
复制
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解析用法和冲突提供了有趣的见解,但它们似乎解决了与我无关的问题。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2021-12-22 01:02:03

出于各种原因--尤其是测试的需要--我习惯于总是以数据结构的形式定义a解析配置,通常是一系列数据结构。ArgumentParser的实际创建是在一个可重用的函数中完成的,该函数简单地从dicts构建解析器。这种方法有许多好处,特别是对于更复杂的项目。

如果您的每个脚本都要转移到该模型,我认为您可能能够检测到该函数中的任何配置冲突并相应地提出,从而避免了从ArgumentParser继承和混乱地理解其内部结构的需要。

我不确定我能很好地理解你的冲突处理需求,所以下面的演示仅仅是寻找重复的选项,如果它看到了,就会提出,但是我认为你应该能够理解这个方法,并评估它是否适用于你的情况。的基本思想是在普通数据结构领域而不是在data解析的拜占庭世界中解决您的问题。

代码语言:javascript
运行
复制
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:])
票数 2
EN

Stack Overflow用户

发布于 2022-04-07 13:11:27

虽然我同意FMc的方法在长期可行性方面可能是更好的方法,但我已经找到了一种将定制处理程序覆盖到ArgumentParser中的方法。

关键是重写_ActionsContainer类,它实际上定义了处理程序函数。然后重写ArgumentParser和_ArgumentGroup继承的基类。

在下面的例子中,我只添加了一个忽略任何冲突的处理程序,但是您可以添加任何您想要的自定义逻辑。

代码语言:javascript
运行
复制
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打印:

代码语言:javascript
运行
复制
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打印:

代码语言:javascript
运行
复制
Namespace(a=1)

运行python custom_conflict_handler.py -a 5打印:

代码语言:javascript
运行
复制
Namespace(a=5)
票数 3
EN

Stack Overflow用户

发布于 2021-12-22 21:48:27

基于FMcs方法,我创建了一些更详细的内容,我知道这不是代码评审,但反馈仍然是受欢迎的。还有,也许这能让人看到更多的东西。

代码语言:javascript
运行
复制
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'])
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70442764

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档