如何使用 argparse 处理自动参数解析中的列表

how to handle lists in automatic argument parsing with argparse

对于我所有的 python 代码,我定义了我的程序参数的字典,然后 argparse 在解析调用序列时使用它来设置命令行参数和参数类型:

import argparse

def get_args(pars):
    parser = argparse.ArgumentParser(description='Dynamic arguments')

    # add each key of the default dictionary as an argument expecting the same type 
    for key,val in pars.items():
        parser.add_argument('--'+key,type=type(val))
    newpars=vars(parser.parse_args())

    # Missing arguments=None need to be overwritten with default value
    for key,val in newpars.items():
        if val==None:
            newpars[key]=pars[key]
    return(newpars)

if __name__ == "__main__":

    # default values:
    pars={"a":1,"animal":"cat","f":3.3}
    pars=get_args(pars)
    print(pars)

这适用于解析浮点数、整数和字符串...

python3 test.py --f 2.1
{'a': 1, 'animal': 'cat', 'f': 2.1}

但现在我想添加一个可以是数字列表的参数,例如假设我的默认 pars 字典是这样的:

pars={"a":1,"animal":"cat","f":3.3,"l":[1]}

现在发生这种情况:

python3 test.py --l [1,2,3]
{'a': 1, 'animal': 'cat', 'f': 3.3, 'l': ['[', '1', ',', '2', ',', '3', ']']}

这不是我想要的,我想要一个数字列表。过去,当我手动一个接一个地处理参数时,我会通过使用

来解决这个问题
import ast 
val=ast.literal_eval("[1,2,3]")
[1, 2, 3]

但是我不能在每个参数上使用 ast.literal_eval,因为当没有传递包含数字参数的字符串时它会失败,所以我想试试这个:

    try:
       parser.add_argument('--'+key,type=type(ast.literal_eval(val)))
    except:     
       parser.add_argument('--'+key,type=type(val))

并将默认列表定义为

l="[1]"

this then returns 作为列表参数,但我如何选择它以在解析中有条件地使用 ast.literal_eval ?

将参数作为 Python 列表传递有点“烦人”,因为所有参数都是字符串,因此您需要将其解析为列表。如果您愿意妥协将列表作为 space 分隔的参数传递,例如 --l 1 2 3,那么一种可能的方法是使用 nargs。只需在循环中添加一个 try/except 块来检查参数是否可迭代:

for key, val in pars.items():
    try:
        typ = type(val[0])
        nargs = "+"
    except TypeError:
        typ = type(val)
        nargs = None

    parser.add_argument('--'+key, nargs=nargs, type=typ)

一些注意事项:

  • 这允许传递任意长度的列表。如果你想将大小限制为字典中的列表,只需更改为 nargs = len(val).

  • 这只能允许同类列表 - 里面的所有对象必须是同一类型。

  • 我使用了 nargs = None 而不是 nargs = 1 因为后者实际上会使参数成为一个 1 项列表,而前者会将参数作为单个对象传递。


将以上代码嵌入您的代码(并使用下面的第二个 par,以提高可读性)将导致:

pars = {"a": 1, "animal": "cat", "f": 3.3, "l": [1]}

>>> python3 test.py --f 2.1 --l 1 2
{'a': 1, 'animal': 'cat', 'f': 2.1, 'l': [1, 2]}

>>> python3 test.py --f 2.1 --l 1 2.2
test.py: error: argument --l: invalid int value: '2.2'

type 参数应该是一个接受字符串的函数 (callable),return 是一个值,否则会引发错误。

在内置函数中,intfloat 大约是唯一可用的函数:

In [40]: int("12")
Out[40]: 12
In [41]: float("12.3")
Out[41]: 12.3

其他像 listbool 不 return 天真的用户所期望的:

In [42]: list("[1,2,3]")
Out[42]: ['[', '1', ',', '2', ',', '3', ']']
In [43]: bool("False")
Out[43]: True

因此,如果你想接受'--flag False'(而不是使用'store_true' action),你需要一个自定义函数

def mybool(astr):
   if astr in ['False', 'no', 'No']:
       return False
   elif ...

接受列表的预期方式是使用 nargs 指定数量。

$python myscript.py --foo [1,2,3] --bar a b c --baz [1 2]

翻译成sys.argv

['myscript.py', '--foo', '[1,2,3]', '--bar', 'a', 'b', 'c', '--baz', '[1', '2]']

nargs='+' 将处理 '--bar' 情况,但不会处理其他情况。

'--foo' 可以用 type=ast.literal_evaljson.loads 处理。但这绕过了 argparsenargstype=int.

提供的保护措施