如何将任意选项字符串解析为python字典

Posted

技术标签:

【中文标题】如何将任意选项字符串解析为python字典【英文标题】:How to parse an arbitrary option string into a python dictionary 【发布时间】:2012-08-14 03:47:30 【问题描述】:

我正在尝试找到最 Pythonic 的方式来获取包含命令行选项的字符串:

"-t 500 -x -c 3 -d"

然后把它变成字典

"-t":"500", "-x":True, "-c":"3", "-d": True

UPDATE:字符串还应该能够包含 --long 选项,以及中间带破折号的单词:

"-t 500 -x -c 3 -d --long-option 456 -testing weird-behaviour"

在建议我查看 OptionParse 模块之前,请记住我不知道有效选项是什么或类似的东西,我只是想将字符串放入字典中以允许根据不同的字典对其进行修改选项。

我正在考虑的方法是使用 split() 将项目放入列表中,然后遍历列表并查找以破折号“-”开头的项目并将它们用作键,然后以某种方式到达值列表中的下一项。我遇到的问题是没有值的选项。我想过做类似的事情:

for i in range(0, len(opt_list)):
        if opt_list[i][0] == "-":
            if len(opt_list) > i+1 and not opt_list[i+1][0] == "-":
                opt_dict[opt_list[i]] = opt_list[i+1] 
            else:
                opt_dict[opt_list[i]] = True

但是当我这样做时,我似乎是在用 C 而不是 Python 编程......

【问题讨论】:

list[i][0] == '-' -> lst.startswith('-')(不要使用 listdict 作为变量名——这可能会导致糟糕的一天)。您也可以使用enumerate,但这可能没有太大帮助... 感谢startswith() 指针。是的,我没有将它们用作变量名,只是在此示例中对其进行了更改以避免混淆。 命令行选项可以加引号吗?是否有任何-- 标志可以防止后续参数被解释为标志? Plazgoth:我在答案中添加了一个编辑,以解释您想要的内容实际上不可能用任意选项列表明确解析(并允许值以“-”开头) 【参考方案1】:

我不能以最Pythonic 的方式说话,但这里有一个 1-liner:

opt_list = "-t 500 -x -c 3 -d"

dict((e if len(e) >1 else (e[0],True) for e in (elem.split() 
      for elem in ('-'+d for d in opt_list.split('-') if d))))

>>>'-t': '500', '-x': True, '-c': '3', '-d': True

[编辑:正如 Matthias 指出的那样,这不适用于带有“-”的值]

...但是,总的来说,当您在选项值中允许使用“-”时,我认为 OP 的答案无法明确解决。

考虑这些简单的选项:

“-a -b”

这是:

'-a': '-b', 'a-':真,'-b':真

???

【讨论】:

"-" 上进行巧妙的拆分,而不是(看似)更自然的" "。我想如果你把它分成一个更容易理解的多线,你可能在这里有一个不错的解决方案...... 我喜欢 1-liner 的方法,但我需要一点时间去理解 如果我们对选项和值一无所知,如果值为weird-behaviour会发生什么。 @Matthias:好吧,它不会起作用......但如果一个值是,让我们说:-10.0 大多数其他解决方案也不起作用......不确定是否有真的是一个万无一失的方法。 同意我不想防止在单词中间出现破折号,所以我会避免在'-'上拆分【参考方案2】:
>>> results = "-t 500 -x -c 3 -d".split()
>>> rd = 
>>> while i < len(results):
...    if results[i].startswith("-"):
...       rd[results[i]]=True
...       try:
...          if not results[i+1].startswith("-"):
...             rd[results[i]] = results[i+1]
...       except IndexError: pass
...    i+=1
...
>>> rd
'-t': '500', '-x': True, '-c': '3', '-d': True

但与你所拥有的非常相似..

【讨论】:

我认为 gerrats 解决方案更好......它会选择多字选项【参考方案3】:

它最初出现的一个更难的问题,这是我的第一次尝试。它只是遍历参数并检查它们是否以- 开头。如果是这样,而下一个参数没有,则将这两项添加到字典中,否则将添加当前参数和 True。如果参数列表中的最后一项以- 开头,则需要try

args = "-t 500 -x -c 3 -d".split()

d = 

for i, item in enumerate(args):
    if item.startswith('-'):
        try:
            if args[i+1].startswith('-'):
                d[item] = True
            else:
                d[item] = args[i+1]
        except IndexError:
                d[item] = True

print d # prints '-t': '500', '-x': True, '-c': '3', '-d': True

编辑: 受Gerrat's splitting on - 启发的替代解决方案如下:

args = "-t 500 -x -c 3 -d".split('-')

d = 

for arg in args:
    if arg:
        try:
            k, v = arg.split()
        except ValueError:
            k, v = arg.strip(), True

        d[k] = v

但是,正如 Matthias 指出的那样,如果选项和值中包含 -s,这可能不起作用。

【讨论】:

同意我不想防止在单词中间出现破折号,所以我会避免在'-'上拆分【参考方案4】:

你可以像这样使用正则表达式:

import re

args = "-t 500 -x -c 3 -d --long-option 456 -testing weird-behaviour"
matches = re.findall(r'(--?[\w-]+)(.*?)(?= -|$)', args)

result = 
for match in matches:
    result[match[0]] = True if not match[1] else match[1].strip()

print result

结果等于


'-d': True, 
'-c': '3', 
'-t': '500', 
'--long-option': '456', 
'-x': True, 
'-testing': 'weird-behaviour'


正则表达式分解:

(--?[\w-]+)(.*?)(?= -|$)

(--?[\w-]+) 匹配以“-”或“--”开头的任何字符或单词(单词中允许使用破折号)。 (.*?) 使用问号匹配 non-greedy or minimal fashion 中的任何字符 0 次或多次。 (?= -|$) 是一个积极的前瞻性。它会检查我们要查找的内容是否后跟“-”或字符串的结尾,但它不会将其包含在匹配项中。

注意这个正则表达式中括号的使用。这些用于创建组,因此当我们调用 findall 时,它会将它们拆分为元组。

【讨论】:

太棒了,我认为这是最 Pythonic 的。 我不明白你的正则表达式中的 (.*?)(?= -|$)。这是什么意思? @StephaneRolland 我刚吃完午饭回来。我已经添加了正则表达式的细分 - 我希望它有所帮助! 一般来说,我远离正则表达式只是因为我认为在多个所有者之间维护它们很痛苦。对一个人有意义的事情需要一段时间才能让下一个人理解。忽略这一点,您认为哪个更有效?使用“re”还是像我的示例中那样使用循环? +1 表示干净的正则表达式。但是如果需求没有得到解决,你最终会重新实现类似 shlex.split() 的东西,example in my answer。还有result = k: True if not v.strip() else v.strip() for k, v in matches 注意:if 内的 v.strip() 即,您可能需要在正则表达式中使用 \s*【参考方案5】:
import re

myDictionnary = 

strPattern1 = "-[0-9a-z ]*"
strPattern2 = "-([0-9a-z]+) *(.*)"
strToParse = "-t 500 -x -c 3 -d"

listKeyValues = re.findall(strPattern1, strToParse)

for kv in listKeyValues:

    match = re.search(strPattern2, kv)

    key = match.group(1)
    value = match.group(2)

    if len(value) > 0:
        myDictionnary[key] = value
    else:
        myDictionnary[key] = True

【讨论】:

【参考方案6】:

人类的参数解析 - https://github.com/kennethreitz/args

【讨论】:

【参考方案7】:

要正确处理引号内的空格,您可以使用shlex.split():

import shlex

cmdln_args = ('-t 500 -x -c 3 -d --long-option 456 '
              '-testing "weird -behaviour" -m "--inside"')

args = shlex.split(cmdln_args)
options = k: True if v.startswith('-') else v
           for k,v in zip(args, args[1:]+["--"]) if k.startswith('-')

from pprint import pprint
pprint(options)

输出

'--inside': True,
 '--long-option': '456',
 '-c': '3',
 '-d': True,
 '-m': True,
 '-t': '500',
 '-testing': 'weird -behaviour',
 '-x': True

【讨论】:

+1 - 感谢您对我的回答的详细评论,这是一个很好的解决方案。 +1 表示能够处理带引号的字符串和空格的更怪异行为的详细解决方案。 根据我对示例的理解,似乎 -m 选项的值应为“--inside”,而代码似乎将它们解释为单独的选项。 @Plazgoth:不,这是预期的行为。 shlex.split() 模拟 shell 的工作方式。试试:python -c "import sys; print(sys.argv)" -m "--inside"。注意:-m--inside 的处理方式相同。 +1 表示我从未见过的字典理解能力!?这个表格叫什么名字?

以上是关于如何将任意选项字符串解析为python字典的主要内容,如果未能解决你的问题,请参考以下文章

python里如何将字典转为json字符串

如何将 Python 字典序列化为字符串,然后再返回字典?

将字符串与字典值完全匹配并检索键 - python

Python - 如何搜索字典键以查看它是否包含某个字符串

python中的字典

将任意长度的字典项展平为 Python 中的路径列表