argparse.add_argument() 中的 type=dict
Posted
技术标签:
【中文标题】argparse.add_argument() 中的 type=dict【英文标题】:type=dict in argparse.add_argument() 【发布时间】:2011-11-29 08:38:36 【问题描述】:我正在尝试将字典设置为可选参数(使用 argparse);以下是我目前所拥有的:
parser.add_argument('-i','--image', type=dict, help='Generate an image map from the input file (syntax: \'name\': <name>, \'voids\': \'#08080808\', \'0\': \'#00ff00ff\', \'100%%\': \'#ff00ff00\').')
但是运行脚本:
$ ./script.py -i 'name': 'img.png','voids': '#00ff00ff','0': '#ff00ff00','100%': '#f80654ff'
script.py: error: argument -i/--image: invalid dict value: 'name:'
即使在解释器内部,
>>> a='name': 'img.png','voids': '#00ff00ff','0': '#ff00ff00','100%': '#f80654ff'
工作得很好。
那么我应该如何传递参数呢? 提前致谢。
【问题讨论】:
您可以从外部文件或标准输入中读取JSON等格式,然后对其进行解析。所以你的 argparse 类型实际上是一个文件。 正如@wim 在他的回答中所说,shell 在将参数传递给 python 之前正在处理它们。如果你在命令前加上'echo' (echo ./script.py -i 'name': ...
),你会看到 python 看到了什么(主要是它没有收到任何引号)。在您的参数中没有 $
的情况下(可以被 shell 解释为环境变量),您可以用双引号将您的 dict 括起来:./script.py -i "'name': 'img.png', ...."
【参考方案1】:
删除这个:json.loads
也可以在这里工作。好像不太脏。
import json
import argparse
test = '"name": "img.png","voids": "#00ff00ff","0": "#ff00ff00","100%": "#f80654ff"'
parser = argparse.ArgumentParser()
parser.add_argument('-i', '--input', type=json.loads)
args = parser.parse_args(['-i', test])
print(args.input)
返回:
u'0': u'#ff00ff00', u'100%': u'#f80654ff', u'voids': u'#00ff00ff', u'name': u'img.png'
【讨论】:
json.loads
是type
的不错选择。像int
和float
一样,它接受一个字符串,如果它不能处理它,则返回一个ValueError
。它也比eval
更安全。为此,它可能有点过于笼统(即它可以处理列表'[1, 2]'
),但用户可以在parse_args()
之后处理它。
当值为str
ings、int
、float
时,它工作正常。对于其他类型的值,例如bool
,它不会(但将1
传递给True
应该可以工作,但所有代码都写得很好)。
你能添加一个cli输入的例子吗?几乎我尝试的所有结果都是invalid loads value
。例如,这有效,。 --input=""
这失败了--input="'foo': 'bar'"
@J'e --input='"foo" : "bar" '
会起作用,因为 JSON 语法要求字符串使用双引号。【参考方案2】:
为了完整起见,与 json.loads 类似,您可以使用 yaml.load(可从 PyPI 中的 PyYAML 获得)。这与 json 相比具有优势,因为不需要在命令行上引用单个键和值,除非您试图将整数强制转换为字符串或以其他方式克服 yaml 转换语义。但显然整个字符串需要引用,因为它包含空格!
>>> import argparse
>>> import yaml
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('-fna', '--filename-arguments', type=yaml.load)
>>> data = "location: warehouse A, site: Gloucester Business Village"
>>> ans = parser.parse_args(['-fna', data])
>>> print ans.filename_arguments['site']
Gloucester Business Village
尽管在给出的问题中不可否认,许多键和值必须被引用或改写以防止 yaml 发生错误。如果您需要数字而不是字符串值,使用以下数据似乎效果很好:
>>> parser.add_argument('-i', '--image', type=yaml.load)
>>> data = "name: img.png, voids: 0x00ff00ff, '0%': 0xff00ff00, '100%': 0xf80654ff"
>>> ans = parser.parse_args(['-i', data])
>>> print ans.image
'100%': 4161164543L, 'voids': 16711935, 'name': 'img.png', '0%': 4278255360L
【讨论】:
更正(缺少)parser.parse_args 调用。感谢您指出这一点,Hotschke 现在必须使用yaml.safe_load
,或者使用 functools.partial 传递 Loader
参数【参考方案3】:
使用简单的 lambda 解析非常灵活:
parser.add_argument(
'--fieldMap',
type=lambda x: k:int(v) for k,v in (i.split(':') for i in x.split(',')),
help='comma-separated field:position pairs, e.g. Date:0,Amount:2,Payee:5,Memo:9'
)
【讨论】:
这是迄今为止最干净的解决方案!谢谢,帮了大忙 非常干净的解决方案,谢谢。由于 v 在多种情况下使用,它给我造成了混乱,下面也可以type=lambda e: k:int(v) for k,v in (x.split(':') for x in e.split(',')),
【参考方案4】:
我敢打赌,你的 shell 弄乱了大括号,因为花括号是许多 shell 中用于大括号扩展功能的语法(请参阅 here)。
传入复杂的容器(例如字典),要求用户了解 Python 语法,这在命令行界面中似乎是一个糟糕的设计选择。相反,我建议只在 CLI 中的 argument group 中一个接一个地传递选项,然后从解析的组中以编程方式构建 dict。
【讨论】:
在 3.11 版中更改:不推荐在参数组上调用 add_argument_group()。此功能从未受到支持,并且并不总是正常工作。该函数通过继承偶然存在于 API 上,将来将被删除。根据 API 文档,可以在下面更详细地参考docs.python.org/dev/library/… @AnuragUpadhyaya 那又怎样?这个答案建议在解析器上添加一个参数组,而不是在返回的组上。该功能本身并没有被删除,只是对该功能的不相关滥用..【参考方案5】:结合来自@Edd 的type=
和来自@Bradley 的ast.literal.eval
产生最直接的解决方案,IMO。它允许直接检索 argval,甚至为 dict 采用(引用的)默认值:
代码 sn-p
parser.add_argument('--params', '--p', help='dict of params ', type=ast.literal.eval, default="'name': 'adam'")
args = parser.parse_args()
运行代码
python test.py --p "'town': 'union'"
注意 dict 值的引号。此引用适用于 Windows 和 Linux(使用 [t]csh 测试)。
检索 Argval
dict=args.params
【讨论】:
【参考方案6】:你绝对可以在参数解析器中输入一些看起来像字典文字的东西,但你必须引用它,所以当 shell 解析你的命令行时,它会以
单个参数而不是多个参数(空格字符是正常的参数分隔符) 正确引用(shell 在解析过程中删除引号,因为它使用它们进行分组)所以这样的东西可以将你想要的文本输入到你的程序中:
python MYSCRIPT.py -i "\"name\": \"img.png\", \"voids\": \"#00ff00ff\",\"0\": \"#ff00ff00\",\"100%\": \"#f80654ff\""
但是,这个字符串不是 dict 构造函数的有效参数;相反,它是一个有效的 python 代码 sn-p。你可以告诉你的参数解析器这个参数的“类型”是eval
,这会起作用:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-i','--image', type=eval, help='Generate an image map...')
args = parser.parse_args()
print args
并调用它:
% python MYSCRIPT.py -i "\"name\": \"img.png\", \"voids\": \"#00ff00ff\",\"0\": \"#ff00ff00\",\"100%\": \"#f80654ff\""
Namespace(image='0': '#ff00ff00', '100%': '#f80654ff', 'voids': '#00ff00ff', 'name': 'img.png')
但这并不安全;输入可以是任何东西,并且您正在评估任意代码。它同样笨拙,但以下会更安全:
import argparse
import ast
parser = argparse.ArgumentParser()
parser.add_argument('-i','--image', type=ast.literal_eval, help='Generate an image map...')
args = parser.parse_args()
print args
这也有效,但对eval
'd 的限制要大得多。
不过,让用户在命令行上输入一些内容,并正确引用,看起来像 python 字典,这非常笨拙。而且,您必须在事后进行一些检查,以确保它们通过字典而不是其他可评估的东西,并且其中包含正确的键。如果满足以下条件,则更易于使用:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--image-name", required=True)
parser.add_argument("--void-color", required=True)
parser.add_argument("--zero-color", required=True)
parser.add_argument("--full-color", required=True)
args = parser.parse_args()
image =
"name": args.image_name,
"voids": args.void_color,
"0%": args.zero_color,
"100%": args.full_color
print image
为:
% python MYSCRIPT.py --image-name img.png --void-color \#00ff00ff --zero-color \#ff00ff00 --full-color \#f80654ff
'100%': '#f80654ff', 'voids': '#00ff00ff', 'name': 'img.png', '0%': '#ff00ff00'
【讨论】:
哇,感谢您提供的可能性概述;然而,尽管在示例中我只输入了 0 和 100%,但它们实际上可以是任何值(例如 '46%':'#0f0e0d0c','3629','#f0e0d0c0'),这是没有考虑的在你的最后一段代码中......【参考方案7】:我发现的最简单的方法之一是将字典解析为列表,然后将其转换为字典。例如使用 Python3:
#!/usr/bin/env python3
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-i', '--image', type=str, nargs='+')
args = parser.parse_args()
if args.image is not None:
i = iter(args.image)
args.image = dict(zip(i, i))
print(args)
然后你可以在命令行输入类似:
./script.py -i name img.png voids '#00ff00ff' 0 '#ff00ff00' '100%' '#f80654ff'
得到想要的结果:
Namespace(image='name': 'img.png', '0': '#ff00ff00', 'voids': '#00ff00ff', '100%': '#f80654ff')
【讨论】:
【参考方案8】:一般建议:不要使用 eval。
如果你真的必须... “评估”是危险的。如果您确定没有人会故意输入恶意输入,请使用它。即使那样,也可能有缺点。我已经介绍了一个不好的例子。
虽然使用 eval 代替 json.loads 也有一些优势。 dict 并不需要是有效的 json。因此, eval 在接受“字典”方面可能非常宽松。我们可以通过确保最终结果确实是一个 python 字典来处理“危险”部分。
import json
import argparse
tests = [
'"name": "img.png","voids": "#00ff00ff","0": "#ff00ff00","100%": "#f80654ff"',
'"a": 1',
"'b':1",
"'$abc': '$123'",
'"a": "a" "b"' # Bad dictionary but still accepted by eval
]
def eval_json(x):
dicti = eval(x)
assert isinstance(dicti, dict)
return dicti
parser = argparse.ArgumentParser()
parser.add_argument('-i', '--input', type=eval_json)
for test in tests:
args = parser.parse_args(['-i', test])
print(args)
输出:
Namespace(input='name': 'img.png', '0': '#ff00ff00', '100%': '#f80654ff', 'voids': '#00ff00ff')
Namespace(input='a': 1)
Namespace(input='b': 1)
Namespace(input='$abc': '$123')
Namespace(input='a': 'ab')
【讨论】:
这是非常危险的建议。使用 eval 将(显然)导致来自 cmdline 的输入被评估为 python。这发生在之前它返回一个值,所以你的类型检查太少太晚了。此外,还有很多有效的危险 python 仍然会返回一个 dict....“我们可以处理危险”的说法既不准确,也很危险,作为建议传播。 好的。同意。我正在更改答案的语言。【参考方案9】:从命令行将参数作为字典传递的最小示例:
# file.py
import argparse
import json
parser = argparse.ArgumentParser()
parser.add_argument("-par", "--parameters",
required=False,
default=None,
type=json.loads
)
args = parser.parse_args()
print(args.parameters)
在终端中,您可以使用字符串格式将参数作为字典传递:
python file.py --parameters '"a":1'
【讨论】:
【参考方案10】:你可以试试:
$ ./script.py -i "'name': 'img.png','voids': '#00ff00ff','0': '#ff00ff00','100%': '#f80654ff'"
我还没有在我的手机上测试过这个。
编辑:顺便说一句,我同意@wim,我认为将字典的每个 kv 作为参数对用户来说会更好。
【讨论】:
【参考方案11】:这是另一个解决方案,因为我必须自己做类似的事情。我使用ast
模块将作为字符串输入到终端的字典转换为字典。这很简单。
代码 sn-p
说下面叫test.py
:
import argparse
import ast
parser = argparse.ArgumentParser()
parser.add_argument('--params', '--p', help='dict of params ',type=str)
options = parser.parse_args()
my_dict = options.params
my_dict = ast.literal_eval(my_dict)
print(my_dict)
for k in my_dict:
print(type(my_dict[k]))
print(k,my_dict[k])
然后在终端/cmd 行中,你会写:
运行代码
python test.py --p '"name": "Adam", "lr": 0.001, "betas": (0.9, 0.999)'
输出
'name': 'Adam', 'lr': 0.001, 'betas': (0.9, 0.999)
<class 'str'>
name Adam
<class 'float'>
lr 0.001
<class 'tuple'>
betas (0.9, 0.999)
【讨论】:
【参考方案12】:TLDR 解决方案: 最简单快捷的解决方案如下:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-par", "--parameters",
default=,
type=str)
args = parser.parse_args()
在parser.add_argument
函数中:
-
使用字典对象作为默认对象
str
作为类型
然后args.parameters
将自动转换为字典,不需要ast.literal.eval
或json.loads
。
动机:
@Galuoises 和 @frankeye 发布的方法在将 default
设置为如下所示的 json 编码字典时似乎不起作用。
parser.add_argument("-par", "--parameters",
required=False, default="\"k1\":v1, \"k2\":v2",
type=json.loads)
这是因为
【讨论】:
【参考方案13】:以下工作正常:
parser = argparse.ArgumentParser()
parser.add_argument("-par", "--parameters",
required=False, default="k1a":"v1a","k2a":"v2a",
type=json.loads)
args = parser.parse_args()
print(str(parameters))
result:
'k1a': 'v1a', 'k2a': 'v2a'
对于默认值,类型应该是dict,因为json.loads返回一个字典,而不是一个字符串,默认对象应该是一个字典。
import argparse,json,sys
sys.argv.extend(['-par','"k1b":"v1b","k2b":"v2b"'])
parser = argparse.ArgumentParser()
parser.add_argument("-par", "--parameters",
required=False, default="k1":"v1","k2":"v2",
type=json.loads)
args = parser.parse_args()
print(str(args.parameters))
result:
'k1b': 'v1b', 'k2b': 'v2b'
【讨论】:
以上是关于argparse.add_argument() 中的 type=dict的主要内容,如果未能解决你的问题,请参考以下文章
Groovy闭包 Closure ( 闭包中调用 Groovy 脚本中的方法 | owner 与 delegate 区别 | 闭包中调用对象中的方法 )