Python argparse 中存在命名冲突时如何从命令行获取 2 个不同对象的参数

How to get arguments for 2 different objects from the command line when there is a naming conflict in Python argparse

我有两个 classes,A 和 B,每个都有自己定义的参数解析器(使用 argparse) 我现在想向 A 添加功能,以便它调用 class B。我正在使用组合来执行此操作(即 A 具有对象 B 的实例)

我在这里询问如何组合两个 arg 解析对象,以便 argparseA 现在将在问题 中包含 argparseB 中的参数 我的问题如下:A 和 B 都有同名参数。但是-我需要用户输入两个不同的值(即 argpaseA.val1 需要获取值 argparseA.val1 和 argParseB.val1)

(显而易见的解决方案是在 argparseA 或 argpaseB 中重命名 val1,但是已经有超过 50 个脚本继承了 class A,还有 50 个脚本继承了 class B,所以我想要对 A 和 B 的更改尽可能少。)

我想向 argpaseA 添加一个新的且命名不同的参数,称为 val2,然后可以将其作为 val1 传递给 argparseB。

我的问题是 - 进行这种从 argparseA 到 argparseB 的转换或参数的正确方法是什么?

或者有更好的设计方法吗?

我猜你正在尝试我的 parents 建议,并说明可能发生的情况。但即使您采用了另一种方法,这也可能有所帮助。

import argparse

parserA=argparse.ArgumentParser()
a1=parserA.add_argument('-f','--foo','--bar')
print(a1)
print()

parserB=argparse.ArgumentParser()
b1=parserB.add_argument('-g','--goo','--bar')
print(b1)
b1.dest='bar'    # can change attributes like dest after creation
print()

# parser with parents; not the conflict_handler
parserC=argparse.ArgumentParser(conflict_handler='resolve',
    parents=[parserA, parserB])
print(parserC._actions)   # the actions (arguments) of C
print()
parserA.print_help()
print()
parserC.print_help()   # uses the C._actions

产生

1445:~/mypy$ python3 stack38071986.py 
_StoreAction(option_strings=['-f', '--foo', '--bar'], dest='foo',      
    nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)

_StoreAction(option_strings=['-g', '--goo', '--bar'], dest='goo', 
    nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)

[_StoreAction(option_strings=['-f', '--foo'], dest='foo', nargs=None, 
    const=None, default=None, type=None, choices=None, help=None, metavar=None), 
 _HelpAction(option_strings=['-h', '--help'], dest='help', nargs=0, 
    const=None, default='==SUPPRESS==', type=None, choices=None, help='show this help message and exit', metavar=None), 
 _StoreAction(option_strings=['-g', '--goo', '--bar'], dest='bar', 
    nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)]

usage: stack38071986.py [-f FOO]

optional arguments:
  help               show this help message and exit
  -f FOO, --foo FOO

usage: stack38071986.py [-f FOO] [-h] [-g BAR]

optional arguments:
  -f FOO, --foo FOO
  -h, --help            show this help message and exit
  -g BAR, --goo BAR, --bar BAR

正常的 store 行为是将 value 存储在 args 命名空间 setattr(namespace, action.dest, value)dest 属性中。 dest 默认是第一个长选项字符串(减去 --),但可以设置为参数(对于可选),或在创建后设置。

当新操作的一个或多个选项字符串(标志)与现有操作冲突时,将引发 conflict_handler。默认处理程序会引发错误,但在这里我使用 resolve 处理程序。它试图删除足够的现有参数来解决冲突。

所有 3 个解析器都创建了一个 -h(帮助)操作。 parserC 中的 resolve 删除除一个以外的所有内容。请注意,parserA 帮助中缺少 [-h]

parserB 中定义的 --barparserA 中的相同字符串冲突。 resolve 已将其从 A 的定义中删除,但 -f--foo 保留。

parserC 的创建扰乱了其他解析器;所以我建议在 运行.

中只使用 parserC.parse_args()

我们可以写一个不同的 conflict_handler 方法。 resolve 有一些粗糙的边缘,不经常使用。

我正在使用一些未记录的功能。有些人认为这是不安全的。但如果你想要不寻常的行为,你就必须接受一些风险。此外,argparse 文档并不是关于其行为的最终定论,而且比代码本身更容易更改。开发人员在进行更改时几乎对向后冲突感到疑惑。

==================

这里尝试自定义 resolve 冲突处理程序

import argparse

def pp(adict):
    for k in adict:
        v=adict[k]
        print('Action %10s:'%k,v.option_strings, v.dest)

def new_resolve(self, action, conflicting_actions):
    rename_dict={'--var':'--var1'}
    for option_string, action in conflicting_actions:
        new_string = rename_dict.get(option_string, None)
        if new_string:
            # rename rather than replace
            print(action.option_strings)
            action.option_strings = [new_string]
            action.dest = new_string[2:]

            pp(self._option_string_actions)
            a1=self._option_string_actions.pop(option_string, None)
            print(a1)
            self._option_string_actions[new_string] = a1
            pp(self._option_string_actions)

        else:
            # regular remove action
            action.option_strings.remove(option_string)
            self._option_string_actions.pop(option_string, None)

            # if the option now has no option string, remove it from the
            # container holding it
            if not action.option_strings:
                action.container._remove_action(action)

argparse._ActionsContainer._handle_conflict_resolve=new_resolve

parserA=argparse.ArgumentParser()
a1=parserA.add_argument('-f','--foo')
a1=parserA.add_argument('--var')

parserB=argparse.ArgumentParser()
b1=parserB.add_argument('-g','--goo')
b1=parserB.add_argument('--var')

parserC=argparse.ArgumentParser(conflict_handler='resolve',
   parents=[parserA, parserB],
   add_help=False)

parserA.print_help()
print()
parserC.print_help()
print(parserC.parse_args())

产生

1027:~/mypy$ python3 stack38071986.py --var1 1 --var 3
['--var']
Action      --var: ['--var1'] var1
Action         -g: ['-g', '--goo'] goo
Action      --foo: ['-f', '--foo'] foo
Action         -h: ['-h', '--help'] help
Action      --goo: ['-g', '--goo'] goo
Action     --help: ['-h', '--help'] help
Action         -f: ['-f', '--foo'] foo
_StoreAction(option_strings=['--var1'], dest='var1', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)
Action         -g: ['-g', '--goo'] goo
Action     --var1: ['--var1'] var1
Action      --foo: ['-f', '--foo'] foo
Action         -h: ['-h', '--help'] help
Action      --goo: ['-g', '--goo'] goo
Action     --help: ['-h', '--help'] help
Action         -f: ['-f', '--foo'] foo

usage: stack38071986.py [-f FOO] [--var1 VAR1]

optional arguments:
  help               show this help message and exit
  -f FOO, --foo FOO
  --var1 VAR1

usage: stack38071986.py [-f FOO] [--var1 VAR1] [-h] [-g GOO] [--var VAR]

optional arguments:
  -f FOO, --foo FOO
  --var1 VAR1
  -h, --help         show this help message and exit
  -g GOO, --goo GOO
  --var VAR

Namespace(foo=None, goo=None, var='3', var1='1')

冲突处理函数定义在一个超级class中,所以我不能只用subclass ArgumentParser来添加一个新的。添加自定义 ActionFormatHandler class 很容易,但这并不容易。我的猜测是没有人尝试过这种定制。

所以我的笨拙是编写 resolve 方法的修改,并且 link 它在运行中。不干净,但足以进行测试。

此处理程序知道现有操作(action 参数)的身份,但不知道新操作的身份(即来自 ParserA 的 --var,但不知道来自 ParserB 的 --var) .所以我正在修改那个现有的 'name' 。现在,此方法必须通过其 rename_dict 知道要替换的选项字符串和新名称。

完成此操作后,我认为编写 parents 机制的自定义版本可能会更容易。一个可以用作:

parserC = argparse.ArgumentParser()
parserC.add_argument(...)   # C's own arguments
copy_arguments(parserC, [parserA, parserB], rename_dict={...})

============================

我更喜欢这个 - 自定义 parents 机制,让我可以指定 skip_listreplace_dict。 (我可能会删除关于解决方案)。

import argparse

def add_actions(parser, other, skip_list=None, rename_dict=None):
    # adapted from _add_container_actions (used for parents)
    # copy (by reference) selected actions from other to parser
    # can skip actions (to avoid use of conflict_handler)
    # can rename other actions (again to avoid conflict)
    if skip_list is None:
        skip_list = ['-h','--help']
    if rename_dict is None:
        rename_dict = {}

    # group handling as before
    # collect groups by titles
    title_group_map = {}
    for group in parser._action_groups:
        if group.title in title_group_map:
            msg = _('cannot merge actions - two groups are named %r')
            raise ValueError(msg % (group.title))
        title_group_map[group.title] = group

    # map each action to its group
    group_map = {}
    for group in other._action_groups:

        # if a group with the title exists, use that, otherwise
        # create a new group matching the other's group
        if group.title not in title_group_map:
            title_group_map[group.title] = parser.add_argument_group(
                title=group.title,
                description=group.description,
                conflict_handler=group.conflict_handler)

        # map the actions to their new group
        for action in group._group_actions:
            group_map[action] = title_group_map[group.title]

    # add other's mutually exclusive groups
    # NOTE: if add_mutually_exclusive_group ever gains title= and
    # description= then this code will need to be expanded as above
    for group in other._mutually_exclusive_groups:
        mutex_group = parser.add_mutually_exclusive_group(
            required=group.required)

        # map the actions to their new mutex group
        for action in group._group_actions:
            group_map[action] = mutex_group

    # add all actions to this other or their group

    # addition with skip and rename
    for action in other._actions:
        option_strings = action.option_strings
        if any([s for s in option_strings if s in skip_list]):
             print('skipping ', action.dest)
             continue
        else:
            sl = [s for s in option_strings if s in rename_dict]
            if len(sl):
                 mod = rename_dict[sl[0]]
                 action.dest = action.dest+mod
                 action.option_strings = [option_strings[0]+mod]
        group_map.get(action, parser)._add_action(action)

parserA=argparse.ArgumentParser()
a1=parserA.add_argument('-f','--foo')
a1=parserA.add_argument('--var')

parserB=argparse.ArgumentParser()
b1=parserB.add_argument('-g','--goo')
b1=parserB.add_argument('--var')

parserC=argparse.ArgumentParser()
# parserC.add_argument('baz')
add_actions(parserC, parserA, rename_dict={'--var':'A'})
add_actions(parserC, parserB, rename_dict={'--var':'B'})

parserC.print_help()
print(parserC.parse_args())

和一个样本 运行

2245:~/mypy$ python3 stack38071986_1.py --varA 1 --varB 3
skipping  help
skipping  help

usage: stack38071986_1.py [-h] [-f FOO] [--varA VARA] [-g GOO] [--varB VARB]

optional arguments:
  -h, --help         show this help message and exit
  -f FOO, --foo FOO
  --varA VARA
  -g GOO, --goo GOO
  --varB VARB
Namespace(foo=None, goo=None, varA='1', varB='3')

=============================

如果我加上

print('parserC actions')
for action in parserC._actions:
    print(action.option_strings, action.dest)

我得到这个打印输出

parserC actions
['-h', '--help'] help
['-f', '--foo'] foo
['--varA'] varA
['-g', '--goo'] goo
['--varB'] varB

_actions 是解析器的动作(参数)列表。它是 'hidden',因此请谨慎使用,但我预计不会像这样对基本 属性 进行任何更改。您可以修改这些操作的许多属性,例如 dest

例如,如果我重命名一些 dest:

for action in parserC._actions:
    if action.dest.startswith('var'):
        action.dest = action.dest+'_C'
print(parserC.parse_args())

命名空间看起来像

Namespace(foo=None, goo=None, varA_C=None, varB_C='one')