为什么即使指定了参数,argparse 也会包含可选参数的默认值?

Why does argparse include default value for optional argument even when argument is specified?

我在 Python 3.6 中使用 argparse。我正在使用可选参数来收集我的程序参数。对于其中一些,我有合理的默认值,因此我使用该参数的默认值配置解析器。

In [2]: import argparse
   ...: import shlex
   ...: 
   ...: parser = argparse.ArgumentParser()
   ...: parser.add_argument('-s', '--samples', action='store', nargs='+', type=int, required=True,
   ...:                     help='number of samples')
   ...: parser.add_argument('-r', '--regions', action='append', nargs='+', type=str, default=['all'],
   ...:                     help='one or more region names. default to [\'all\']')

当未指定 -r/--regions 参数时,我希望看到配置的默认值(我确实看到了)。

In [3]: s = '-s 37'
   ...: parser.parse_args(shlex.split(s))
Out[3]: Namespace(regions=['all'], samples=[37])

指定 -r/--regions 参数时,我希望看到 我随参数提供的值,但默认值也会显示。

In [5]: s = '-s 37 -r foo'
...: parser.parse_args(shlex.split(s))
Out[5]: Namespace(regions=['all', ['foo']], samples=[37])

这不是我所期望的。我希望默认值仅在可选参数不存在时存在。我单步执行了 argparse 代码。我找不到包含默认值的位置。基于 comments,逻辑似乎是在 处理提供的实际参数值之前将默认值添加到结果命名空间 。我本来希望它是相反的(即仅当您到达 args 的末尾并且没有看到具有默认值的参数时才应用默认值。

任何人都可以阐明这一点吗?我是否错误地使用或理解了可选参数的默认选项的用途?有没有办法实现我正在寻求的行为(即,如果未提供可选的,则使用命名空间中的默认值)?

您是正确的,使用 append 操作和非空默认值,任何提供的值都会附加到默认值而不是替换它 - 这是 append 的预期行为。

对于append动作,更合适的代码如下

parser.add_argument('-r', '--regions', action='append', type=str)
parser.parse_args('-r foo -r foo2'.split())
Namespace(regions=['foo', 'foo2'])

您会在原始代码中注意到 nargs='+' 结果区域值是一个列表列表。追加操作已经使变量成为一个列表,因此不需要 nargs。

然后提供一个被解析器覆盖的默认值,使默认值在解析器的名称之外 space,例如

_DEFAULT_REGIONS = ['all']

parser = argparse.ArgumentParser()
parser.add_argument('-r', '--regions', action='append', type=str,
                    help="Defaults to %s" % (_DEFAULT_REGIONS))
parser.parse_args(<..>)

regions = parser.regions \
    if parser.regions is not None else _DEFAULT_REGIONS
function_using_regions(regions)

如果提供,则使用 parser.regions,否则使用 _DEFAULT_REGIONS

处​​理默认值的逻辑是在解析开始时将所有默认值插入到 namespace 中。然后让解析替换它们。然后在解析结束时有一段复杂的逻辑:

for each value in the namespace
   if it is a string and equals the default
      evaluate the string (with `type`) and put it back

对于普通的 store 操作,这很好用,并且允许您提供默认值作为字符串或您选择的任何值。

使用 append 这会产生意想不到的值。它将 ['all'] 放在命名空间上,然后将新值附加到它上面。由于您的 nargs 是“+”,它会附加一个列表,从而导致字符串和列表的混合。

append 操作无法判断它是将新值附加到 default 提供的列表中,还是附加到之前几个 appends 的结果列表中。使用 None 默认值,它将创建一个空列表,并附加到该列表。

虽然这不会像您预期的那样执行,但它实际上给了您很多控制权。

解决此问题的最简单方法是将默认值保留为 None。解析后,只要检查这个属性是否is None,如果是,就用你的['all']替换它。它并不邪恶,也不违背 argparse 开发者的设计意图。解析完所有输入后,事情就容易多了。

此问题已在 Python bug/issues、http://bugs.python.org/issue16399 上提出,并且可能之前已在 SO 上提出。但我怀疑补丁能做的最好的事情就是在文档中添加注释,类似于 optparse:

中的注释

"The append action calls the append method on the current value of the option. This means that any default value specified must have an append method. It also means that if the default value is non-empty, the default elements will be present in the parsed value for the option, with any values from the command line appended after those default values".

有关编写您自己的追加 Action 子类的想法,请参阅 bug/issue。

您只需删除 nargs 参数并使用 action=append

parser = argparse.ArgumentParser()
parser.add_argument('-i', default=[], action='append', type=str)
parser.parse_args('-i foo'.split())
# result >> Namespace(i=['foo'])

对于我的情况,我不想检查参数是否为 None 然后检查长度。