【问题标题】:Python: pull fields from formatted stringPython:从格式化字符串中提取字段
【发布时间】:2016-10-15 18:34:51
【问题描述】:

我有一个格式为键/值对的字符串列表,由空格分隔。例如,一条消息可能是:

"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=' 的问题,这样就无法区分主题结束和消息开始的位置,那很好,但现在我愿意忽略这个问题。李>

目前我拥有的最好的是:

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'])

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

【问题讨论】:

  • 值可以包含任何东西吗?这会是一个有效的主题吗? subject=a message=This is a message=subject=message=1=hello。您应该避免在值字符串中使用= 字符。
  • @HåkenLid 的主题和消息值可以包含任何内容。其余的更可靠地固定。是的,你的例子是一个有效的主题。我想处理 = 和主题和消息中的标签。我现在唯一愿意忽略的是包含" message=" 的主题。这与消息的开头是不可能区分的。但是例如包含" subject=" 的消息是可管理的,因为标签是固定且有序的。
  • 我希望提出这种荒谬消息格式的人被解雇。
  • @HåkenLid 它是连接到远程系统的 CLI 的输出。它需要简洁但可读。除了人类阅读之外,它从未打算被任何其他人解析。

标签: python regex parsing dictionary


【解决方案1】:

好的,赶紧扔东西

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)

【讨论】:

    【解决方案2】:

    一点横向思考:拆分“=”,将每个标签留在前一行的末尾。例如:

    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'}
    

    你仍然需要转换数字,并从表格的最后一行恢复消息的最后一个单词。我可以在线执行这些操作,但我认为它们会有点阻塞演示文稿。

    【讨论】:

      【解决方案3】:

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

      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= 的边缘情况。

      【讨论】:

      【解决方案4】:

      您可以使用正则表达式和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))
      

      【讨论】:

      【解决方案5】:

      我想出了一些可以让您使用自定义转换器的方法。

      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')
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-12-07
        • 1970-01-01
        • 2023-01-07
        • 2015-01-25
        • 1970-01-01
        • 2014-11-17
        • 1970-01-01
        • 2017-12-31
        相关资源
        最近更新 更多