解析换行分隔文件

Parsing newline delimited file

我正在做一个项目,我想在其中使用 Python 解析文本文件。该文件由一些格式不同的块的数据条目组成。有新行时会找到新条目。这就是我想要完成的:

  1. 跳过前几行(前 16 行)
  2. 第16行后,有一个换行符开始新的数据输入
  3. 阅读以下行,直到遇到新的换行符。每行都附加到名为数据的列表中。
  4. 该列表将被传递给处理进一步处理的函数。
  5. 重复步骤 3 和 4,直到文件中没有更多数据

以下是文件示例:

Header Info
More Header Info

Line1
Line2
Line3
Line4
Line5
Line6
Line7
Line8
Line9
Line10
Line11
Line12
Line13

MoreInfo    MoreInfo    MoreInfo    MoreInfo    MoreInfo
MoreInfo2   MoreInfo2   MoreInfo2   MoreInfo2   MoreInfo2   MoreInfo2
MoreInfo3   MoreInfo3   MoreInfo3   MoreInfo3   MoreInfo3
MoreInfo4   MoreInfo4
FieldName1  0001    0001
FieldName1  0002    0002
FieldName1  0003    0003
FieldName1  0004    0004
FieldName1  0005    0005
FieldName2  0001    0001
FieldName3  0001    0001
FieldName4  0001    0001
FieldName5  0001    0001
FieldName6  0001    0001

MoreInfo    MoreInfo    MoreInfo    MoreInfo    MoreInfo
MoreInfo2   MoreInfo2   MoreInfo2   MoreInfo2   MoreInfo2   MoreInfo2
MoreInfo3   MoreInfo3   MoreInfo3   MoreInfo3   MoreInfo3
MoreInfo4   MoreInfo4
FieldName1  0001    0001
FieldName1  0002    0002
FieldName1  0003    0003
FieldName1  0004    0004
FieldName1  0005    0005
FieldName2  0001    0001
FieldName3  0001    0001
FieldName4  0001    0001
FieldName5  0001    0001
FieldName6  0001    0001

这是我编写的一些代码。它能够读取第一个块并将其附加到列表中:

with open(loc, 'r') as f:
    for i in range(16):
        f.readline()

    data = []
    line = f.readline()
    if line == "\n":
        dataLine = f.readline()
        while dataLine != "\n":
            data.append(dataLine)
            dataLine = f.readline()

    #pass data list to function
    function_call(data)
    # reset data list here?
    data = []

我怎样才能让它适用于整个文件?我的假设是使用 "with open",它充当 "while not end of file"。我尝试在跳过前 16 行后添加 "while True"。 我对Python的解析能力知之甚少。

在此先感谢您的帮助。

在初始跳过后添加一个 while True 绝对有效。当然,你必须把所有的细节都弄好。

可以尝试扩展您已有的方法,在外循环中使用嵌套的while循环。但将其视为单个循环可能更容易。对于每一行,您可能只需要做三件事:

  • 如果 没有行,因为你在 EOF,break 退出循环,确保处理旧的 data (文件中的最后一个块)如果有第一个。
  • 如果它是一个空行,开始一个新的 data,确保先处理旧的 data 如果有的话。
  • 否则,附加到现有的 data

所以:

with open(loc, 'r') as f:
    for i in range(16):
        f.readline()

    data = []
    while True:
        line = f.readline()
        if not line:
            if data:
                function_call(data)
            break
        if line == "\n":
            if data:
                function_call(data)
                data = []
        else:
            data.append(line)

有几种方法可以进一步简化此过程:

  • 使用 for line in f: 而不是重复执行 f.readline() 并检查的 while 循环。
  • 使用groupby将行迭代器转换为以空行分隔的行组迭代器。

如果您仍然为此苦苦挣扎,这里有一个使用 itertools.groupby() 和关键函数 search():

读取示例数据的实现
from itertools import groupby, repeat

def search(d):
    """Key function used to group our dataset"""

    return d[0] == "\n"

def read_data(filename):
    """Read data from filename and return a nicer data structure"""

    data = []

    with open(filename, "r") as f:
        # Skip first 16 lines
        for _ in repeat(None, 16):
            f.readline()

        # iterate through each data block
        for newblock, records in groupby(f, search):
            if newblock:
                # we've found a new block
                # create a new row of data
                data.append([])
            else:
                # we've found data for the current block
                # add each row to the last row
                for row in records:
                    row = row.strip().split()
                    data[-1].append(row)

    return data

这将产生一个嵌套的块列表数据结构。每个子列表由数据文件中的 \n 分组分开。

您文件中块的模式是它们由以空行或文件末尾终止的行组组成。这个逻辑可以封装在一个生成器函数中,该函数迭代地从您的文件中生成行块,这将简化脚本的其余部分。

下面的getlines()是生成函数。另请注意,文件的前 17 行被跳过以到达第一个块的开头。

from pprint import pformat

loc = 'parsing_test_file.txt'

def function(lines):
    print('function called with:\n{}'.format(pformat(lines)))

def getlines(f):
    lines = []
    while True:
        try:
            line = next(f)
            if line != '\n':  # not end of the block?
                lines.append(line)
            else:
                yield lines
                lines = []
        except StopIteration:  # end of file
            if lines:
                yield lines
            break

with open(loc, 'r') as f:
    for i in range(17):
        next(f)

    for lines in getlines(f):
        function(lines)

print('done')

使用您的测试文件输出:

function called with:
['MoreInfo    MoreInfo    MoreInfo    MoreInfo    MoreInfo\n',
 'MoreInfo2   MoreInfo2   MoreInfo2   MoreInfo2   MoreInfo2   MoreInfo2\n',
 'MoreInfo3   MoreInfo3   MoreInfo3   MoreInfo3   MoreInfo3\n',
 'MoreInfo4   MoreInfo4\n',
 'FieldName1  0001    0001\n',
 'FieldName1  0002    0002\n',
 'FieldName1  0003    0003\n',
 'FieldName1  0004    0004\n',
 'FieldName1  0005    0005\n',
 'FieldName2  0001    0001\n',
 'FieldName3  0001    0001\n',
 'FieldName4  0001    0001\n',
 'FieldName5  0001    0001\n',
 'FieldName6  0001    0001\n']
function called with:
['MoreInfo    MoreInfo    MoreInfo    MoreInfo    MoreInfo\n',
 'MoreInfo2   MoreInfo2   MoreInfo2   MoreInfo2   MoreInfo2   MoreInfo2\n',
 'MoreInfo3   MoreInfo3   MoreInfo3   MoreInfo3   MoreInfo3\n',
 'MoreInfo4   MoreInfo4\n',
 'FieldName1  0001    0001\n',
 'FieldName1  0002    0002\n',
 'FieldName1  0003    0003\n',
 'FieldName1  0004    0004\n',
 'FieldName1  0005    0005\n',
 'FieldName2  0001    0001\n',
 'FieldName3  0001    0001\n',
 'FieldName4  0001    0001\n',
 'FieldName5  0001    0001\n',
 'FieldName6  0001    0001\n']
done