【问题标题】:type=dict in argparse.add_argument()argparse.add_argument() 中的 type=dict
【发布时间】: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', ....}"

标签: python dictionary argparse


【解决方案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函数中:

            1. 使用字典对象作为默认对象
            2. 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'}
              

              【讨论】:

                猜你喜欢
                • 2016-05-16
                • 1970-01-01
                • 2020-12-10
                • 1970-01-01
                • 1970-01-01
                • 2022-10-13
                • 2016-10-01
                • 1970-01-01
                • 2016-01-15
                相关资源
                最近更新 更多