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.loadstype 的不错选择。像intfloat 一样,它接受一个字符串,如果它不能处理它,则返回一个ValueError。它也比eval 更安全。为此,它可能有点过于笼统(即它可以处理列表'[1, 2]'),但用户可以在parse_args() 之后处理它。 当值为strings、intfloat时,它工作正常。对于其他类型的值,例如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.evaljson.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的主要内容,如果未能解决你的问题,请参考以下文章

ppwjs之前端达人

新版测试中

初一下期中试卷

c++理解协程05

Access中VBA中excel文件中的VLookup

Groovy闭包 Closure ( 闭包中调用 Groovy 脚本中的方法 | owner 与 delegate 区别 | 闭包中调用对象中的方法 )