Python:从格式化字符串中提取字段

时间:2016-06-14 20:59:25

标签: python regex parsing dictionary

我有一个字符串列表,这些字符串被格式化为键/值对,用空格分隔。例如,消息可能是:

"time=2016/06/14 16:44:00.000 level=1 sequenceNum=35 user=Username subject=subject goes here message=This is a message"

键/值对将始终按此顺序排列,并且消息将始终采用此形式。我想将这个字符串转换为这种形式的字典:

{'level': 1,
 'message': 'This is a message',
 'sequenceNum': 35,
 'subject': 'subject goes here',
 'time': '2016/06/14 16:44:00.000',
 'user': 'Username'}

有几点需要注意:

  1. 我希望levelsequenceNum是数字,而不是字符串
  2. 时间戳,主题和消息中可以有空格,所以我不能只是分隔空格
  3. 邮件和主题可能包含任何内容,因此我无法在标签或等号上进行拆分。然而,它们将始终是第二个到最后一个并且在字符串中的最后一个东西。如果我们可以解决可能包含字符串'message='的主题的问题,这将使得无法区分主题结束和消息开始的地方,这很好,但是现在我还是愿意忽视这个问题。
  4. 目前我所拥有的最好的是:

    item = {}
    item['time'] = message[5:message.index('level=')].strip()
    message = message[message.index('level='):]
    item['level'] = int(message[6:message.index('sequenceNum=')].strip())
    message = message[message.index('sequenceNum='):]
    #etc.
    

    我不是很喜欢这个,即使它显然工作正常。我希望有一种更优雅的方式来实现基于字符串格式化。例如,如果我尝试创建此字符串,我可以使用它:

    "time=%s level=%s sequenceNum=%s user=%s subject=%s message=%s" % (item['time'], item['level'], item['sequenceNum'], item['user'], item['subject'], item['message'])
    

    我想知道是否可以在另一个方向上这样做。

5 个答案:

答案 0 :(得分:2)

为此我会使用正则表达式。这可能不是最快(性能方面)或最简单(理解)的解决方案,但它肯定会有效。 (并且可能是你最接近“反向格式”)

import re

pattern = re.compile(
    "time=(?P<time>.+)\s"
    "level=(?P<level>\d+)\s"
    "sequenceNum=(?P<sequenceNum>\d+)\s"
    "user=(?P<user>\w+)\s"
    "subject=(?P<subject>.+?)\s"    # <-- EDIT: changed from greedy '.+' to non-greedy '.+?'
    "message=(?P<message>.+)"
    )

lines = ["time=2016/06/14 16:44:00.000 level=1 sequenceNum=35 user=Username subject=subject goes here message=This is a message",
         "time=2016/06/14 16:44:00.000 level=1 sequenceNum=35 user=Username subject=subject goes here message=This is a message=hello"]

for line in lines:
    match = pattern.match(line)
    item = match.groupdict()
    print(item)

要将数字作为数字,您可以执行result['level'] = int(result['level'])

之类的操作

如果您感兴趣,我可以扩展一下我如何构建正则表达式以及如何改进它。

编辑:更改了表达式,以涵盖message=在主题中的边缘情况。

答案 1 :(得分:2)

您可以使用正则表达式和re.findall()来查找每个键值对。这种方法的优点是它可以使用任何key=value对字符串。

import re
data = ("time=2016/06/14 16:44:00.000 level=1 sequenceNum=35 "
        "user=Username subject=subject goes here message=This is a message")
matches =  re.findall(r'(\w+)=([^=]+)(?:\s|\Z)', data)
{key: int(val) if val.isdigit() else val for key, val in matches}

在输出中,所有看起来像整数的值都已转换为int

{'level': 1,
'message': 'This is a message',
'sequenceNum': 35,
'subject': 'subject goes here',
'time': '2016/06/14 16:44:00.000',
'user': 'Username'}

如果您不需要将数字转换为整数,那就更简单了:

dict(re.findall(r'(\w+)=([^=]+)(?:\s|\Z)', data))

这是正则表达式(\w+)=([^=]+)(?:\s|\Z)

的regex101解释

如果您的输入数据包含以下内容:"subject=message=subject=message=subject",则表示您遇到问题,因为它不明确。您必须清理输入,或者只是引发异常。

if data.count('=') != 6:
    raise ValidationError('malformed input data: {}'.format(data))

答案 2 :(得分:0)

好吧,快点扔东西

inputString = "time=2016/06/14 16:44:00.000 level=1 sequenceNum=35 user=Username subject=subject goes here message=This is a message"

keys = []
#key is always the element before the '=' sign
for segment in inputString.split('='):
    keys.append(segment.split(" ")[-1])

values = inputString.split("=")
for i in range(len(values)):
    #split the values by the spaces
    tmp = values[i].split(" ")
    #and remove the last part --> the part before the equals sign
    tmp.pop(-1)
    #join them back together
    values[i] = ' '.join(tmp)
#the first element is now empty, because there is no value before the first '='
values.pop(0)

#the last element will be missing in this case, because it will be interpretet as yet another key
if ' ' in inputString.split("=")[-1]:
    values[-1] += ' '+inputString.split("=")[-1].split(' ')[-1]
else:
    #if the last element does not contain a space it will be missing entirely --> adding it back in
    values.pop(-1)
    values += inputString.split("=")[-1]

# combining it to a dict
outputDict = dict(zip(keys, values))
print(outputDict)

答案 3 :(得分:0)

一点横向思维:分开'=',将每个标签留在上一行的末尾。例如:

in_stuff = "time=2016/06/14 16:44:00.000 level=1 sequenceNum=35"
           +" user=Username message=This is a message"
skew = in_stuff.split('=')
table = [entry.split() for entry in skew]
out_dict = {table[i][-1] : ' '.join(table[i+1][:-1]) 
            for i in range(len(table)-1)}
print out_dict

这还没有完成,但说明了这个想法。输出:

{'sequenceNum': '35',
 'level': '1',
 'message': 'This is a',
 'user': 'Username',
 'time': '2016/06/14 16:44:00.000'}

您仍然需要转换数字,并从的最后一行恢复消息的最后一个字。我可以在线进行这些操作,但认为他们会稍微阻塞演示文稿。

答案 4 :(得分:0)

我想出了一些允许你使用自定义转换器的东西。

class Convert(object):
    def __init__(self, *args):
        self.content = ' '.join(args)

    def sequenceNum(self):
        return int(self.content)

    def level(self):
        return int(self.content)

    def __getattr__(self, name):
        def wrapper(*args, **kwargs):
            return self.content
        return wrapper


def line_to_dict(s):
    r = {}
    s_split = s.split('=')
    l = len(s_split) - 1
    k = None

    for i, content in enumerate(s_split):
        if not i:
            k = content
            continue

        content_split = content.split()

        if i == l:
            r[k] = getattr(Convert(*content_split), k)()
        else:
            next_k = content_split.pop()

        r[k] = getattr(Convert(*content_split), k)()

        k = next_k

    return r


if __name__ == "__main__":
    print line_to_dict('time=2016/06/14 16:44:00.000 level=1 sequenceNum=35 user=Username message=This is a message')