使用argparse(python)创建变量键/值对

ddo*_*org 6 python configuration argparse

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

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

所以基本上我想定义--conf-*为一个参数,哪里*可以是任何键,后面会是什么value.

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

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

fra*_*lau 15

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

mycommand par1 --set foo=hello bar="hello world" baz=5
Run Code Online (Sandbox Code Playgroud)

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()
Run Code Online (Sandbox Code Playgroud)

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

结果是一个字符串列表,例如["foo=hello", "bar=hello world", "baz=5"]in args.set,我们现在需要解析它(注意 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
Run Code Online (Sandbox Code Playgroud)

在这一点上它非常简单:

# parse the key-value pairs
values = parse_vars(args.set)
Run Code Online (Sandbox Code Playgroud)

你现在有一本字典:

values = {'foo':'hello', 'bar':'hello world', 'baz':'5'}
Run Code Online (Sandbox Code Playgroud)

请注意值如何始终作为字符串返回。

此方法也记录为git gist

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

hpa*_*ulj 6

我尝试的第一件事是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'}
Run Code Online (Sandbox Code Playgroud)

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))
Run Code Online (Sandbox Code Playgroud)

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

parser.add_argument('--conf', nargs=2, action='append')
Run Code Online (Sandbox Code Playgroud)

会产生:

namespace('conf': [['key1','value1'],['key2','value2']])
Run Code Online (Sandbox Code Playgroud)

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

我相信有关于接受'"key1:value""key2:value2"'输入的问题.


Cra*_*ger 5

这一切都可以更简单地使用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.")
Run Code Online (Sandbox Code Playgroud)


小智 5

到处都有部分答案。让我们总结一下标准的 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()
Run Code Online (Sandbox Code Playgroud)

和用法:

python main.py --filters foo=1 bar="coucou"
Run Code Online (Sandbox Code Playgroud)