重新格式化 Python 中的文本

Reformatting text in Python

我是 python 的新手,在解析日志文件时遇到很大困难。你能帮我理解如何以最 Pythonic 的方式完成以下内容吗?

----- Log Entry 5 -----
Time       : 2016-07-12 09:00:00
Animal     : Brown Bear
Bird       : White Owl
Fish       : Salmon


----- Log Entry 6 -----
Time       : 2016-07-12 09:00:00
Animal     : Brown Bear
Bird       : Parrot
Fish       : Tuna


----- Log Entry 7 -----
Time       : 2016-07-12 09:00:00
Animal     : Lion
Bird       : White Owl
Fish       : Sword Fish


----- Log Entry 8 -----
Time       : 2016-07-12 09:15:00
Animal     : Lion
Bird       : White Owl
Fish       : Sword Fish

所需输出 1:我想将日志重新格式化为如下所示:

Time: 2016-07-12 09:00:00 Animal: Brown Bear  Bird: White Owl  Fish : Salmon
Time: 2016-07-12 09:00:00 Animal: Brown Bear  Bird: Parrot     Fish : Tuna
Time: 2016-07-12 09:00:00 Animal: Lion        Bird: White Owl  Fish : Sword Fish
Time: 2016-07-12 09:15:00 Animal: Lion        Bird: White Owl  Fish : Sword Fish

所需输出 2:然后我希望能够查询时间戳并获得计数摘要:

Time: 2016-07-12 09:00:00
Name:       Count:
Brown Bear  2
Lion        1
White Owl   2
Parrot      1
Salmon      1
Tuna        1
Sword Fish  1

Time: 2016-07-12 09:15:00
Name:       Count:
Lion        1
White Owl   1
Sword Fish  1

到目前为止我的代码:

import os, sys, time, re, collections, subprocess

show_cmd = 'cat question |  egrep -v \'^$|=|Log\' | awk \'ORS=NR%4?FS:RS\' | grep Time'
log = (subprocess.check_output(show_cmd, shell=True).decode('utf-8'))

def time_field():
    logRegex = re.compile(r'Time\s*:.*\d\d\d-\d\d-\d\d\s\d\d:\d\d')
    log_parsed = (logRegex.findall(log))
    a = (str(log_parsed).replace('  ', ''))
    a = ((' ' + a[1:-1]).split(','))
    for i in a:
        print(i)

time_field()

有很多方法可以做到这一点。就我个人而言,我会避免为此使用正则表达式,因为它可能不会更有效并且表达式变得麻烦且不灵活。这是我想到的:

class Entry:
    def __init__(self):
        self.time = None
        self.animal = None
        self.bird = None
        self.fish = None

    def __repr__(self):
        fmt = "{0} {1} {2} {3}".format(
            "Time: {time: <{width}}",
            "Animal: {animal: <{width}}",
            "Bird: {bird: <{width}}",
            "Fish: {fish: <{width}}")
        return fmt.format(
            time=self.time, animal=self.animal,
            bird=self.bird, fish=self.fish,
            width=12)

    def __radd__(self, other):
            return self.__add__(other)

    def __add__(self, other):
        if type(other) == dict:
            for i in [self.animal, self.bird, self.fish]:
                if i in other: other[i] += 1
                else: other[i] = 1
            return other
        elif type(other) == Entry:
            return self.__add__({}) + other
        else:
            return self.__add__({})

def parse_log(path):
    def extract(line):
        start = line.find(':') + 1
        return line[start:].strip()

    entries = []
    entry = None
    with open(path, 'r') as f:
        for line in f.readlines():
            if line.startswith('-----'):
                if entry: entries.append(entry)
                entry = Entry()
            elif line.startswith('Time'):
                entry.time = extract(line)
            elif line.startswith('Animal'):
                entry.animal = extract(line)
            elif line.startswith('Bird'):
                entry.bird = extract(line)
            elif line.startswith('Fish'):
                entry.fish = extract(line)

        if entry: entries.append(entry)

    return entries


def print_output_1(entries):
    for entry in entries:
        print entry

def print_output_2(entries, time):
    animals = sum([e for e in entries if e.time == time])

    print "Time: {0}".format(time)
    print "Name:        Count:"
    for animal, count in animals.items():
        print "{animal: <{width}} {count}".format(
                animal=animal, count=count, width=12)


logPath = 'log.log'
time = '2016-07-12 09:15:00'
entries = parse_log(logPath)

print_output_1(entries)
print ""
print_output_2(entries, time)

输出(假定 log.log 与您提供的输入匹配)是:

Time: 2016-07-12 09:00:00 Animal: Brown Bear   Bird: White Owl    Fish: Salmon
Time: 2016-07-12 09:00:00 Animal: Brown Bear   Bird: Parrot       Fish: Tuna
Time: 2016-07-12 09:00:00 Animal: Lion         Bird: White Owl    Fish: Sword Fish
Time: 2016-07-12 09:15:00 Animal: Lion         Bird: White Owl    Fish: Sword Fish

Time: 2016-07-12 09:15:00
Name:        Count:
White Owl    1
Sword Fish   1
Lion         1

这段代码的工作方式是使用面向对象的编程来简化我们需要做的任务:存储日志条目,以特定格式表示日志条目,并根据特定格式组合日志条目属性。

首先,注意Entry对象及其属性(self.timeself.animalself.birdself.fish)表示日志中的一个条目.假设存储在其属性中的信息是正确的,可以创建一个方法将该信息表示为格式化字符串。 __repr__() 方法在 python 需要对象的字符串表示时被调用,因此它似乎是放置这段代码的好地方。该方法中大量使用了format函数,但浏览format.

上的python文档后应该清楚其工作原理

需要一种组合这些条目对象的方法才能获得您指定的第二个输出。这可以通过多种方式完成,而我这样做的方式不一定是最好的。我使用了 __radd__()__add__() 方法,它们在对象上使用 + 运算符时被调用。通过这样做,代码 entry1 + entry2sum([entry1, entry2]) 可用于获取两个条目中动物的总和。然而,Entry class 不能用于存储求和结果,因为它不能包含任意信息。相反,我选择使用 dict 对象作为两个 Entry 对象求和的结果。为了对两个以上的 Entry 对象求和,Entry 也必须能够与 dict 对象求和,因为 Entry + Entry + Entry 导致 dict + Entry

__add__() 函数检查它被添加到的对象是否是一个 dict 对象。如果是这种情况,它会检查条目中的每个动物是否已经存在于 dict 中。如果没有,它会将动物添加为键。否则,它将增加该键的值。 __radd__()__add__() 类似,只不过是在一些特殊情况下使用。有关详细信息,请参阅 python 文档。

对于对象是 Entry 的情况,可以编写代码来从每个 Entry 对象中收集所有动物并根据该信息创建一个 dict,但是由于已经有代码可以将 Entry 添加到 dict,因此更容易先将一个对象添加到空 dict,然后将生成的 dict 添加到另一个对象Entry 对象。

对于所有其他对象,Entry 将简单地 return 自身的 dict 描述,或者自身添加一个空的 dict

现在所有的工具都可以实现前面列出的目标。要获得与所需输出 1 匹配的 Entry 的字符串表示,只需要 print entrystrrepr = str(entry)。要获得所需的输出 2,需要做更多的工作,但它只是将具有相同 self.time 属性 的所有条目相加,然后显示生成的字典。

未涵盖的代码的最后一部分是解析日志以创建 Entry 对象列表。该代码只是逐行遍历日志并使用信息填充 Entry。我觉得这很简单,但是如果没有任何意义,您可以随时提问。