ArgumentParser:使用某些参数选项时不强制执行独占组

ArgumentParser: exclusive groups not enforced when using some argument options

import argparse

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('--foo', nargs='?', default=True, const=True)
group.add_argument('--bar', dest='foo', action='store_false')
parser.parse_args(['--foo', '--bar']) # no error

我有点困惑,尽管 --foo--bar 应该是互斥的,但上面的代码没有产生任何错误。这是预期的行为吗?还是我们不应该在独占组中乱用参数选项?

请注意,当 default=True 未作为参数传递给 --foo 时,会发生预期的错误。

我在 Python 2.7.13 和 3.5.3 中观察到了这种行为。

相关代码埋在take_action:

        argument_values = self._get_values(action, argument_strings)

        # error if this argument is not allowed with other previously
        # seen arguments, assuming that actions that use the default
        # value don't really count as "present"
        if argument_values is not action.default:
            seen_non_default_actions.add(action)
            for conflict_action in action_conflicts.get(action, []):
                if conflict_action in seen_non_default_actions:
                    msg = _('not allowed with argument %s')
                    action_name = _get_action_name(conflict_action)
                    raise ArgumentError(action, msg % action_name)

特别是 'argument_values is not action.default' 测试。

可选位置 (nargs='?') 总是“看到, in the sense that an empty list satisfies itsnargs. In that case it gets theaction.defaultvalue. That's handled by a special case in_get_values()”。

但是对于互斥测试,我们不希望这些操作是 'seen',因此这个额外的测试来设置“seen_non_default_actions”集。

is 测试非常严格。这些项目必须具有相同的 id.

您的示例失败,因为 const=True 中的 Truedefault=True.

中的 ID 相同

parser.parse_args(['--foo', '--bar'])foo 设置为 const。但是因为它匹配 default 它是看不见的,并且不会触发独占错误。

通常 constdefault 会有不同的值,利用 '?' 的 3 向解析。

这个 is not 测试在另一个案例中失败了。小于 256 的数字是唯一的。有关更多信息,请访问 http://bugs.python.org/issue18943


测试用例:

In [1]: import argparse
   ...: parser = argparse.ArgumentParser()
   ...: group = parser.add_mutually_exclusive_group()
   ...: a1=group.add_argument('--foo', nargs='?', default=True, const=True)
   ...: a2=group.add_argument('--bar', action='store_false')
   ...: 
In [2]: parser.parse_args(['--foo', '--bar'])
Out[2]: Namespace(bar=False, foo=True)      # the True's match

更改const

In [3]: a1.const
Out[3]: True
In [4]: a1.const='other'
In [5]: parser.parse_args(['--foo', '--bar'])
usage: ipython3 [-h] [--foo [FOO] | --bar]
ipython3: error: argument --bar: not allowed with argument --foo
An exception has occurred, use %tb to see the full traceback.
SystemExit: 2

值在 is 意义上匹配的另一种情况:

In [6]: a1.const=None;a1.default=None
In [7]: parser.parse_args(['--foo', '--bar'])
Out[7]: Namespace(bar=False, foo=None)

对于小数字:

In [8]: a1.const=3;a1.default=3
In [9]: parser.parse_args(['--foo', '--bar'])
Out[9]: Namespace(bar=False, foo=3)

但不是大的:

In [10]: a1.const=300;a1.default=300
In [11]: parser.parse_args(['--foo', '--bar'])
usage: ipython3 [-h] [--foo [FOO] | --bar]
ipython3: error: argument --bar: not allowed with argument --foo
An exception has occurred, use %tb to see the full traceback.
SystemExit: 2

字符串可能很棘手。代码中的字面量是唯一的,但拆分创建的字面量不是:

In [12]: a1.default='test'
In [14]: parser.parse_args(['--foo', 'test', '--bar'])
Out[14]: Namespace(bar=False, foo='test')   # no error

这更像是命令行提供字符串的方式:

In [16]: parser.parse_args('--foo test --bar'.split())
usage: ipython3 [-h] [--foo [FOO] | --bar]
ipython3: error: argument --bar: not allowed with argument --foo
An exception has occurred, use %tb to see the full traceback.
SystemExit: 2