从带引号的字符串中提取键值对

问题描述 投票:0回答:5

我在为这个需求编写一个“优雅”的解析器时遇到了麻烦。 (看起来不像是一份C早餐)。输入是一个字符串,键值对用“,”分隔,并用“=”连接。

key1=value1,key2=value2

欺骗我的部分是值可以用引号引起来 (") ,并且引号内的 ',' 不会结束键。

key1=value1,key2="value2,still_value2"

最后一部分让我使用 split 或 re.split 变得很棘手,求助于 for i in range for 循环:(.

任何人都可以演示一种干净的方法来做到这一点吗?

可以假设引号仅出现在值中,并且没有空格或非字母数字字符。

python parsing
5个回答
13
投票

我建议不要使用正则表达式来完成此任务,因为您要解析的语言不是正则的。

你有一个由多个键值对组成的字符串。解析它的最好方法不是匹配它的模式,而是正确地标记它。

Python 标准库中有一个名为

shlex
的模块,它模仿 POSIX shell 完成的解析,并提供了一个可以轻松根据您的需求进行自定义的词法分析器实现。

from shlex import shlex

def parse_kv_pairs(text, item_sep=",", value_sep="="):
    """Parse key-value pairs from a shell-like text."""
    # initialize a lexer, in POSIX mode (to properly handle escaping)
    lexer = shlex(text, posix=True)
    # set ',' as whitespace for the lexer
    # (the lexer will use this character to separate words)
    lexer.whitespace = item_sep
    # include '=' as a word character 
    # (this is done so that the lexer returns a list of key-value pairs)
    # (if your option key or value contains any unquoted special character, you will need to add it here)
    lexer.wordchars += value_sep
    # then we separate option keys and values to build the resulting dictionary
    # (maxsplit is required to make sure that '=' in value will not be a problem)
    return dict(word.split(value_sep, maxsplit=1) for word in lexer)

split
有一个
maxsplit
参数,使用起来比分割/切片/连接干净得多。)

运行示例:

parse_kv_pairs(
  'key1=value1,key2=\'value2,still_value2,not_key1="not_value1"\''
)

输出:

{'key1': 'value1', 'key2': 'value2,still_value2,not_key1="not_value1"'}

我通常坚持使用 shlex 而不是使用正则表达式(在这种情况下更快)的原因是它给你带来的惊喜更少,特别是如果你稍后需要允许更多可能的输入。我从来没有找到如何用正则表达式正确解析这样的键值对,总会有输入(例如

A="B=\"1,2,3\""
)会欺骗引擎。

如果您不关心此类输入(或者,换句话说,如果您可以确保您的输入遵循正则语言的定义),那么正则表达式就完全没问题。


7
投票

使用拆分字符串、尊重并保留引号中的一些正则表达式魔法,我们可以做到:

import re

string = 'key1=value1,key2="value2,still_value2"'

key_value_pairs = re.findall(r'(?:[^\s,"]|"(?:\\.|[^"])*")+', string)

for key_value_pair in key_value_pairs:
    key, value = key_value_pair.split("=")

根据 BioGeek,我试图猜测,我的意思是解释 Janne Karila 使用的正则表达式:该模式在逗号上中断字符串,但在此过程中尊重双引号部分(可能带有逗号)。 它有两个单独的选项:不涉及引号的字符运行;以及双引号字符运行,其中双引号结束运行,除非它(反斜杠)转义:

(?:              # parenthesis for alternation (|), not memory
[^\s,"]          # any 1 character except white space, comma or quote
|                # or
"(?:\\.|[^"])*"  # a quoted string containing 0 or more characters
                 # other than quotes (unless escaped)
)+               # one or more of the above

3
投票

我想出了这个正则表达式解决方案:

import re
match = re.findall(r'([^=]+)=(("[^"]+")|([^,]+)),?', 'key1=value1,key2=value2,key3="value3,stillvalue3",key4=value4')

这使得“匹配”:

[('key1', 'value1', '', 'value1'), ('key2', 'value2', '', 'value2'), ('key3', '"value3,stillvalue3"', '"value3,stillvalue3"', ''), ('key4', 'value4', '', 'value4')]

然后你可以创建一个for循环来获取键和值:

for m in match:
    key = m[0]
    value = m[1]

2
投票

我不确定它看起来不像C早餐而且相当优雅:)

data = {}
original = 'key1=value1,key2="value2,still_value2"'
converted = ''

is_open = False
for c in original:
    if c == ',' and not is_open:
        c = '\n'
    elif c in ('"',"'"):
        is_open = not is_open
    converted += c

for item in converted.split('\n'):
    k, v = item.split('=')
    data[k] = v

1
投票

根据其他几个答案,我想出了以下解决方案:

import re
import itertools

data = 'key1=value1,key2="value2,still_value2"'

# Based on Alan Moore's answer on http://stackoverflow.com/questions/2785755/how-to-split-but-ignore-separators-in-quoted-strings-in-python
def split_on_non_quoted_equals(string):
    return re.split('''=(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', string)
def split_on_non_quoted_comma(string):
    return re.split(''',(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', string)

split1 = split_on_non_quoted_equals(data)
split2 = map(lambda x: split_on_non_quoted_comma(x), split1)

# 'Unpack' the sublists in to a single list. Based on Alex Martelli's answer on http://stackoverflow.com/questions/952914/making-a-flat-list-out-of-list-of-lists-in-python
flattened = [item for sublist in split2 for item in sublist]

# Convert alternating elements of a list into keys and values of a dictionary. Based on Sven Marnach's answer on http://stackoverflow.com/questions/6900955/python-convert-list-to-dictionary
d = dict(itertools.izip_longest(*[iter(flattened)] * 2, fillvalue=""))

生成的

d
是以下字典:

{'key1': 'value1', 'key2': '"value2,still_value2"'}
© www.soinside.com 2019 - 2024. All rights reserved.