【问题标题】:Create variable key/value pairs with argparse (python)使用 argparse (python) 创建变量键/值对
【发布时间】:2015-01-24 14:39:04
【问题描述】:

我正在使用argparse 模块来设置我的命令行选项。我还在我的应用程序中使用dict 作为配置。简单的键/值存储。

我正在寻找的是一种使用命令行参数覆盖 JSON 选项的可能性,而无需预先定义所有可能的参数。像--conf-key-1 value1 --conf-key-2 value2 这样的东西会创建一个字典{'key_1': 'value1','key_2': 'value2'}(参数中的“-”被字典中的“_”替换)。然后我可以将此 dict 与我的 JSON 配置 (dict) 结合起来。

所以基本上我想将--conf-* 定义为一个参数,其中* 可以是任何键,然后是value

我确实找到了configargparse 模块,但据我所知,我从一个已经使用过的dict 开始。

有什么想法可以解决这个问题吗?

【问题讨论】:

标签: python configuration argparse


【解决方案1】:

我遇到了类似的问题,发现了一个非常可行的模式,可以很好地与 argparse 配合使用(这里有三个密钥对:foo、bar 和 baz:

mycommand par1 --set foo=hello bar="hello world" baz=5

1。定义可选的多值参数

set 参数必须这样定义:

import argparse
parser = argparse.ArgumentParser(description="...")
...
parser.add_argument("--set",
                        metavar="KEY=VALUE",
                        nargs='+',
                        help="Set a number of key-value pairs "
                             "(do not put spaces before or after the = sign). "
                             "If a value contains spaces, you should define "
                             "it with double quotes: "
                             'foo="this is a sentence". Note that '
                             "values are always treated as strings.")
args = parser.parse_args()

参数是可选的和多值的,至少出现一次 (nargs='+')。

结果是字符串的列表,例如args.set 中的 ["foo=hello", "bar=hello world", "baz=5"],我们现在需要对其进行解析(注意 shell 是如何处理和删除引号的!)。

2。解析结果

为此,我们需要 2 个辅助函数:

def parse_var(s):
    """
    Parse a key, value pair, separated by '='
    That's the reverse of ShellArgs.

    On the command line (argparse) a declaration will typically look like:
        foo=hello
    or
        foo="hello world"
    """
    items = s.split('=')
    key = items[0].strip() # we remove blanks around keys, as is logical
    if len(items) > 1:
        # rejoin the rest:
        value = '='.join(items[1:])
    return (key, value)


def parse_vars(items):
    """
    Parse a series of key-value pairs and return a dictionary
    """
    d = {}

    if items:
        for item in items:
            key, value = parse_var(item)
            d[key] = value
    return d

此时就很简单了:

# parse the key-value pairs
values = parse_vars(args.set)

你现在有了一本字典:

values = {'foo':'hello', 'bar':'hello world', 'baz':'5'}

注意这些值是如何始终作为字符串返回的。

此方法也记录为git gist

【讨论】:

  • 谢谢!我只想补充一点,IMO 有一种更简单的方法来解析 dict(map(lambda s: s.split('='), args.set))
  • 非常优雅的解决方案,确实!在您的解决方案中,我可能会保留 parse_var 函数而不是 s.split('=') 以确保它适用于所有边界情况,例如其中一个键以空格等开头。在实践中,如果没有找到=,我也会让它引发错误。
【解决方案2】:

我要尝试的第一件事是使用parse_known_args 处理其他参数,并使用我的例程处理extras 的列表。将“--conf-”处理添加到argparse 会更有效。

argv = '--conf-key-1 value1 --conf-key-2 value2'.split()
p = argparse.ArgumentParser()
args, extras = p.parse_known_args(argv)

def foo(astr):
    if astr.startswith('--conf-'):
        astr = astr[7:]
    astr = astr.replace('-','_')
    return astr

d = {foo(k):v for k,v in zip(extras[::2],extras[1::2])}
# {'key_1': 'value1', 'key_2': 'value2'}

extras 解析可能更健壮 - 确保有正确的对,拒绝格式错误的密钥,处理 =

另一种方法是扫描sys.argv 中的--conf- 字符串,并使用这些字符串构造add_argument 语句。

keys = [k for k in argv if k.startswith('--conf-')]
p = argparse.ArgumentParser()
for k in keys:
    p.add_argument(k, dest=foo(k))
print vars(p.parse_args(argv))

如果您接受 '--conf key1 value1 --conf key2 value2 ...' 作为输入,您可以定义

parser.add_argument('--conf', nargs=2, action='append')

会产生:

namespace('conf': [['key1','value1'],['key2','value2']])

这很容易变成字典。或者自定义的Action 可以使用setattr(namespace, values[0], values[1]) 将键/值对直接输入到命名空间中。

我相信关于接受 '"key1:value" "key2:value2"' 输入存在一些问题。

【讨论】:

  • 听起来像一个方法:)如果没有其他方法,我会尝试这个。我仍然希望 argparse 中有一个“干净”的解决方案。
  • argparse 无法定义选项模式。它可以查找缩写和别名,但不能查找模式。 optparse 有一个自定义操作机制可能能够处理这个问题。
【解决方案3】:

这一切都可以使用str.split(delim, limit) 更简单地完成:

class kvdictAppendAction(argparse.Action):
    """
    argparse action to split an argument into KEY=VALUE form
    on the first = and append to a dictionary.
    """
    def __call__(self, parser, args, values, option_string=None):
        assert(len(values) == 1)
        try:
            (k, v) = values[0].split("=", 2)
        except ValueError as ex:
            raise argparse.ArgumentError(self, f"could not parse argument \"{values[0]}\" as k=v format")
        d = getattr(args, self.dest) or {}
        d[k] = v
        setattr(args, self.dest, d)

...


myparser.add_argument("--keyvalue",
                      nargs=1,
                      action=kvdictAppendAction,
                      metavar="KEY=VALUE",
                      help="Add key/value params. May appear multiple times.")

【讨论】:

    【解决方案4】:

    到处都是部分答案。 总结一下标准的argparse方式。

    import argparse
    class kwargs_append_action(argparse.Action):
    """
    argparse action to split an argument into KEY=VALUE form
    on append to a dictionary.
    """
    
    def __call__(self, parser, args, values, option_string=None):
        try:
            d = dict(map(lambda x: x.split('='),values))
        except ValueError as ex:
            raise argparse.ArgumentError(self, f"Could not parse argument \"{values}\" as k1=v1 k2=v2 ... format")
        setattr(args, self.dest, d)
    
    parser = argparse.ArgumentParser(description="...")
    parser.add_argument("-f", "--filters", dest="filters",
                            nargs='*',
                            default={'k1':1, 'k2': "P"},
                            required=False,
                            action=kwargs_append_action,
                            metavar="KEY=VALUE",
                            help="Add key/value params. May appear multiple times. Aggregate in dict")
    args = parser.parse_args()
    

    及用法:

    python main.py --filters foo=1 bar="coucou"
    

    【讨论】:

      【解决方案5】:

      top answer 的当前代码 有一个错误,如果“parse_var”函数中的“items”变量没有“=”,它会在赋值之前返回变量“value”。这是一个修改/压缩的函数,它为每个参数中是否提供了“=”提供了一些错误处理:

      def parse_vars(items):
          """
              Parse a series of key-value pairs and return a dictionary and 
              a success boolean for whether each item was successfully parsed.
          """
          count = 0
          d = {}
          for item in items:
              if "=" in item:
                  split_string = item.split("=")
                  d[split_string[0].strip()] = split_string[1].strip()
                  count += 1
              else:
                  print(f"Error: Invalid argument provided - {item}")
              
          return d, count == len(items)
      

      【讨论】:

        【解决方案6】:

        为了稍微简化 fralaus 的答案,这两种方法可以很容易地合二为一。

        注意:我的文档字符串等与我将其用于 ansible extra_vars 时有所不同,但字符串拆分的核心逻辑来自 fralaus 的回答。

         def parse_vars(extra_vars):
             """
             Take a list of comma seperated key value pair strings, seperated
             by comma strings like 'foo=bar' and return as dict.
             :param extra_vars: list[str] ['foo=bar, 'key2=value2']
        
             :return: dict[str, str] {'foo': 'bar', 'key2': 'value2'}
             """
             vars_list = []
             if extra_vars:
                 for i in extra_vars:
                    items = i.split('=')
                    key = items[0].strip()
                    if len(items) > 1:
                        value = '='.join(items[1:])
                        vars_list.append((key, value))
             return dict(vars_list)
        
        print parse_vars(args.set)
        
         $ test.py --set blah=gar one=too
        >> {"blah": "gar", "one": "too"}
        

        【讨论】:

          【解决方案7】:

          找到了一个非常简单的解决方案: vars(args) 会将其转换为 dict 对象。

          【讨论】:

            猜你喜欢
            • 2018-06-24
            • 1970-01-01
            • 1970-01-01
            • 2012-06-22
            • 1970-01-01
            • 1970-01-01
            • 2019-05-27
            • 2021-08-05
            • 1970-01-01
            相关资源
            最近更新 更多