正则表达式匹配逗号分隔的key = value列表,其中value可以包含逗号

时间:2013-02-01 07:41:37

标签: python regex parsing python-2.6

我有一个天真的“解析器”,只是做了类似的事情:
[x.split('=') for x in mystring.split(',')]

然而,mystring可以是类似的东西 'foo=bar,breakfast=spam,eggs'

显然,
天真的分裂者不会这样做。为此,我仅限于 Python 2.6标准库 因此,例如pyparsing无法使用。

预期输出为
[('foo', 'bar'), ('breakfast', 'spam,eggs')]

我正在尝试使用正则表达式,但面临以下问题:

我的第一次尝试 r'([a-z_]+)=(.+),?'
给了我 [('foo', 'bar,breakfast=spam,eggs')]

显然,
使.+非贪婪并不能解决问题。

所以,
我猜我必须以某种方式强制使用最后一个逗号(或$) 做到这一点并不真正有效, r'([a-z_]+)=(.+?)(?:,|$)'
与此一样,包含一个值的逗号后面的内容被省略,
例如[('foo', 'bar'), ('breakfast', 'spam')]

我想我必须使用某种后视(?)操作 问题
1. 我使用哪一个?或
2. 如何我这样做/这个?

修改

根据daramarak下面的回答,
我最后做了与abarnert稍后suggested完全相同的事情,稍微冗长一点;

vals = [x.rsplit(',', 1) for x in (data.split('='))]
ret = list()
while vals:
    value = vals.pop()[0]
    key = vals[-1].pop()
    ret.append((key, value))
    if len(vals[-1]) == 0:
        break

编辑2:

为了满足我的好奇心,这实际上是否可以使用正则表达式?即,re.findall()将返回2元组列表?

5 个答案:

答案 0 :(得分:10)

仅用于比较目的,这里的正则表达式似乎也解决了这个问题:

([^=]+)    # key
=          # equals is how we tokenise the original string
([^=]+)    # value
(?:,|$)    # value terminator, either comma or end of string

这里的技巧是限制你在第二组中捕获的内容。 .+吞下=符号,这是我们可以用来区分键和值的字符。完整的正则表达式不依赖于任何反向跟踪(所以它应该与re2之类的东西兼容,如果这是可取的)并且可以使用abarnert的例子。

用法如下:

re.findall(r'([^=]+)=([^=]+)(?:,|$)', 'foo=bar,breakfast=spam,eggs,blt=bacon,lettuce,tomato,spam=spam')

返回:

[('foo', 'bar'), ('breakfast', 'spam,eggs'), ('blt', 'bacon,lettuce,tomato'), ('spam', 'spam')]

答案 1 :(得分:4)

daramarak的答案要么非常接近,要么按原样运作;从样本输出的格式化方式和步骤的模糊描述中很难说清楚。但如果它是非常接近工作的版本,它很容易修复。

将其放入代码:

>>> bits=[x.rsplit(',', 1) for x in s.split('=')]
>>> kv = [(bits[i][-1], bits[i+1][0]) for i in range(len(bits)-1)]

第一行是(我相信)daramarak的回答。就其本身而言,第一行为您提供(value_i, key_i+1)而不是(key_i, value_i)对。第二行是最明显的解决方案。有了更多的中间步骤和一些输出,看它是如何工作的:

>>> s = 'foo=bar,breakfast=spam,eggs,blt=bacon,lettuce,tomato,spam=spam'
>>> bits0 = s.split('=')
>>> bits0
['foo', 'bar,breakfast', 'spam,eggs,blt', 'bacon,lettuce,tomato,spam', 'spam']
>>> bits = [x.rsplit(',', 1) for x in bits0]
>>> bits
[('foo'), ('bar', 'breakfast'), ('spam,eggs', 'blt'), ('bacon,lettuce,tomato', 'spam'), ('spam')]
>>> kv = [(bits[i][-1], bits[i+1][0]) for i in range(len(bits)-1)]
>>> kv
[('foo', 'bar'), ('breakfast', 'spam,eggs'), ('blt', 'bacon,lettuce,tomato'), ('spam', 'spam')]

答案 2 :(得分:1)

我可以建议您像以前一样使用拆分操作。但首先分成等号,然后在最右边的逗号分割,以制作一个左右字符串的单个列表。

input =
"bob=whatever,king=kong,banana=herb,good,yellow,thorn=hurts"

首先会分裂成

first_split = input.split("=")
#first_split = ['bob' 'whatever,king' 'kong,banana' 'herb,good,yellow,thorn' 'hurts']

然后以最右边的逗号分割给你:

second_split = [single_word for sublist in first_split for item in sublist.rsplit(",",1)]
#second_split = ['bob' 'whatever' 'king' 'kong' 'banana' 'herb,good,yellow' 'thorn' 'hurts']

然后你就像这样收集对:

pairs = dict(zip(second_split[::2],second_split[1::2]))

答案 3 :(得分:0)

你能试试吗,它对我有用:

mystring = "foo=bar,breakfast=spam,eggs,e=a"
n = []
i = 0

for x in mystring.split(','):
    if '=' not in x:
        n[i-1] = "{0},{1}".format(n[i-1], x)
    else:
        n.append(x)
        i += 1
print n

你得到的结果如下:

  ['foo=bar', 'breakfast=spam,eggs', 'e=a']

然后你可以简单地浏览列表并做你想做的事。

答案 4 :(得分:0)

假设密钥的名称从不包含,,当,的下一个序列由,=继承时,您可以在=分割}。

re.split(r',(?=[^,=]+=)', inputString)

(这与我原来的解决方案相同。我希望使用re.split,而不是re.findallstr.split

完整的解决方案可以在单行中完成:

[re.findall('(.*?)=(.*)', token)[0] for token in re.split(r',(?=[^,=]+=)', inputString)]