在Python中简单的方法,不读取文件的最后N行

2 投票
4 回答
2280 浏览
提问于 2025-05-01 02:33

我想要逐行读取一个文件,但不想读最后的N行。那我怎么知道在哪儿停下来呢?我不想等到文件读完再回头去丢掉最后的N行。在Python中,问一下要读多少行=X,然后循环读(X-N)行,这样做行不行?

有没有什么最简单、最符合Python风格的方法来做到这一点?

暂无标签

4 个回答

1

因为我们知道必须把文件读到最后才能确定有多少行,所以我尝试了一种“最简单/最符合Python风格”的方法来读取最后的 n 行:

with open(foo, 'r') as f:
    lines = f.readlines()[:-n]
1

要读取文件中除了最后X行以外的所有行,你需要知道最后X行是从哪里开始的。这个信息你需要在某个地方保存下来。

  1. 在写入文件时,记录下最后X行的起始位置。当你到达这个位置时,就停止读取。
  2. 把每一行的起始位置存储在某个地方,这样你就可以在文件后面继续添加内容。
  3. 你知道每行的大小。
    1. 每行的大小都一样,你可以根据文件的总大小来计算。
    2. 每行至少有一个字符,所以你不需要读取最后X个字符。
2

除非你事先知道文件的实际行数,否则你必须读取整个文件。

不过,我猜你是想逐行处理文件,除了最后的N行。你可以在不把整个文件都加载到内存中的情况下做到这一点,只需要保留N行的列表:

with open(file) as fd:
    lines = []
    try:
        for i in range(N):
            lines.append(next(fd))

        i = 0
        for line in fd:
            # process lines[i]
            print (lines[i].rstrip())
            lines[i] = line
            i = (i + 1) % N
    except StopIteration:
        print "less than %d lines" % (N,)
2

三种不同的解决方案:

1) 快速简单,可以参考John的回答:

with open(file_name) as fid:
    lines = fid.readlines()
for line in lines[:-n_skip]:
    do_something_with(line)

这种方法的缺点是你需要先把所有的行都读到内存中,这对于大文件来说可能会有问题。

2) 两次读取

这个方法是对文件进行两次处理,第一次是统计行数 n_lines,第二次只处理前 n_lines - n_skip 行:

# first pass to count
with open(file_name) as fid:
    n_lines = sum(1 for line in fid)

# second pass to actually do something
with open(file_name) as fid:
    for i_line in xrange(n_lines - n_skip):  # does nothing if n_lines <= n_skip
        line = fid.readline()
        do_something_with(line)

这种方法的缺点是你需要遍历文件两次,这在某些情况下可能会比较慢。不过好处是,你在内存中最多只会有一行数据。

3) 使用缓冲区,类似于Serge的解决方案

如果你只想遍历文件一次,只有在知道行 i + n_skip 存在的情况下,才能确定可以处理行 i。这意味着你需要先在一个临时缓冲区中保存 n_skip 行。实现这个的一个方法是使用某种先进先出(FIFO)缓冲区,比如用一个生成器函数来实现一个循环缓冲区:

def fifo(it, n):
    buffer = [None] * n  # preallocate buffer
    i = 0
    full = False
    for item in it:  # leaves last n items in buffer when iterator is exhausted
        if full:
            yield buffer[i]  # yield old item before storing new item
        buffer[i] = item
        i = (i + 1) % n
        if i == 0:  # wrapped around at least once
            full = True

用一系列数字进行快速测试:

In [12]: for i in fifo(range(20), 5):
    ...:     print i,
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

你可以这样使用这个方法处理你的文件:

with open(file_name) as fid:
    for line in fifo(fid, n_skip):
        do_something_with(line)

请注意,这需要足够的内存来临时存储 n_skip 行,但这仍然比第一种方法将所有行都读入内存要好。

这三种方法中哪一种最好,取决于代码的复杂性、内存使用和速度,这些都与具体的应用场景有关。

撰写回答