在Python中简单的方法,不读取文件的最后N行
我想要逐行读取一个文件,但不想读最后的N行。那我怎么知道在哪儿停下来呢?我不想等到文件读完再回头去丢掉最后的N行。在Python中,问一下要读多少行=X,然后循环读(X-N)行,这样做行不行?
有没有什么最简单、最符合Python风格的方法来做到这一点?
4 个回答
因为我们知道必须把文件读到最后才能确定有多少行,所以我尝试了一种“最简单/最符合Python风格”的方法来读取最后的 n
行:
with open(foo, 'r') as f:
lines = f.readlines()[:-n]
要读取文件中除了最后X行以外的所有行,你需要知道最后X行是从哪里开始的。这个信息你需要在某个地方保存下来。
- 在写入文件时,记录下最后X行的起始位置。当你到达这个位置时,就停止读取。
- 把每一行的起始位置存储在某个地方,这样你就可以在文件后面继续添加内容。
- 你知道每行的大小。
- 每行的大小都一样,你可以根据文件的总大小来计算。
- 每行至少有一个字符,所以你不需要读取最后X个字符。
除非你事先知道文件的实际行数,否则你必须读取整个文件。
不过,我猜你是想逐行处理文件,除了最后的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,)
三种不同的解决方案:
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
行,但这仍然比第一种方法将所有行都读入内存要好。
这三种方法中哪一种最好,取决于代码的复杂性、内存使用和速度,这些都与具体的应用场景有关。