argparse 位置多项选择默认子集:无效选择
Posted
技术标签:
【中文标题】argparse 位置多项选择默认子集:无效选择【英文标题】:Argparse positional multiple choices default subset: Invalid Choice 【发布时间】:2021-12-13 11:38:07 【问题描述】:我使用argparse
来获取使用choices
和nargs='*'
的元素子集。如果没有引入任何元素,我想默认返回元素的一个子集,但我得到一个Invalid Choice
错误。
这是我的示例代码:
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument(
"choices",
nargs="*",
type=int,
choices=[1,2,3,4,5],
default=[1,3,5]
)
if __name__ == "__main__":
print(parser.parse_args().choices)
我得到的输出是:
$ ./thing.py 3
[3]
$ ./thing.py 1 2
[1, 2]
$ ./thing.py
usage: thing.py [-h] [1,2,3,4,5 ...]
thing.py: error: argument choices: invalid choice: [1, 3, 5] (choose from 1, 2, 3, 4, 5)
我可以将默认值设置为 int,但不能设置为列表。有什么解决方法吗?
编辑:这似乎只发生在位置参数上。如果在非位置参数中使用 nargs="+"
和 required=False
不会有任何问题。
【问题讨论】:
我最终放弃了使用位置参数,它降低了 CLI 的可读性。我会留下这个问题,以防有人遇到同样的问题并想解决它。 这是一个非常有趣的问题,几乎感觉像是一个错误。我试图开始调查(通过源代码),但现在还没有充分的时间。据我所知,如果您实际传递参数,它们将按以下顺序逐个执行:首先根据type
进行转换,然后检查它们是否在choices
中,然后附加到列表中(因为nargs
) .使用默认值的时候,好像是直接保存到Namespace
,然后签入choices
会失败...
因为*
满足一个空列表,所以它的default
得到特殊处理。检查_get_values
子方法。这包括选择测试,非字符串默认值通常会绕过。 @Tomerikoo
@hpaulj 感谢您的回复。不确定我是否完全理解,但我确实在docs 中看到“如果默认值是字符串,则解析器会解析该值,就好像它是命令行参数一样。特别是,解析器应用任何类型转换参数(如果提供),然后在命名空间返回值上设置属性。否则,解析器将按原样使用该值“但是当我尝试这样做时(default="1"
)我仍然得到invalid choice: '1' (choose from 1, 2, 3, 4, 5)
@Tomerikoo,我在答案中详细说明了这种对默认值的处理。
【参考方案1】:
详细说明我的cmets。
默认值的正常处理,正如@Tomerikoo 引用的那样,是通过type
和choices
检查来处理字符串默认值。默认值在解析开始时放置在 args
命名空间中。最后,如果没有使用输入覆盖默认值,它们将有条件地转换。非字符串默认值保持原样,不检查 type
或 choices
。
但是nargs='*'
的位置不同。它“总是可见”,因为字符串的空列表(即没有)满足其nargs
。使用通常的处理方式,这将覆盖default
,导致Namespace(choices=[])
。
为了解决这个问题,_get_values
方法将[]
替换为default
。这个新值通过choices
测试,但不通过type
。这会导致 OP 遇到的行为。
下面引用_get_values
的相关部分。第一个处理nargs='?'
,第二个'*',和第三个正常情况。 _get_value
进行 type
测试。 _check_value
执行 choices
测试。
def _get_values(self, action, arg_strings):
....
# optional argument produces a default when not present
if not arg_strings and action.nargs == OPTIONAL:
if action.option_strings:
value = action.const
else:
value = action.default
if isinstance(value, str):
value = self._get_value(action, value)
self._check_value(action, value)
# when nargs='*' on a positional, if there were no command-line
# args, use the default if it is anything other than None
elif (not arg_strings and action.nargs == ZERO_OR_MORE and
not action.option_strings):
if action.default is not None:
value = action.default
else:
value = arg_strings
self._check_value(action, value)
# single argument or optional argument produces a single value
elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:
arg_string, = arg_strings
value = self._get_value(action, arg_string)
self._check_value(action, value)
我应该添加另一个块,用于“*”和非空用户输入:
# all other types of nargs produce a list
else:
value = [self._get_value(action, v) for v in arg_strings]
for v in value:
self._check_value(action, v)
请注意,这里的单个字符串是type
和checked
,在第一种情况下,它是整个default
,即checked
。
存在与此相关的错误/问题,但没有人有充分的理由对其进行更改。
【讨论】:
以上是关于argparse 位置多项选择默认子集:无效选择的主要内容,如果未能解决你的问题,请参考以下文章