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

时间:2017-04-27 14:05:36

标签: python argparse

我在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的末尾并且没有看到有默认值的参数时才应用默认值。

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

3 个答案:

答案 0 :(得分:3)

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

对于追加操作,更合适的代码如下

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

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

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

_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

答案 1 :(得分:3)

处理默认值的逻辑是在解析开始时将所有默认值插入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错误/问题,http://bugs.python.org/issue16399已经提出了这个问题,并且可能在此之前已经提到过。但我怀疑补丁可以做的最好的事情就是在文档中添加一个注释,类似于optparse中的这个注释:

  

“append动作在选项的当前值上调用append方法。这意味着指定的任何默认值都必须有append方法。这也意味着如果默认值为非空,则默认元素将是出现在选项的解析值中,命令行中的任何值都附加在这些默认值“。

之后

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

答案 2 :(得分:0)

您只需删除 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 然后检查长度。