如何在Python3.2中倒序读取文件,而不需要将整个文件读入内存?

4

我正在使用Python3.2解析大小为1至10GB的日志文件,需要搜索特定正则表达式(某种时间戳)的行,并希望找到最后一次出现的位置。

我尝试使用以下方法:

for line in reversed(list(open("filename")))

这导致性能非常糟糕(在好的情况下),并且在不好的情况下会出现MemoryError。
在线程中:使用Python倒序读取文件,我没有找到任何好的答案。
我发现以下解决方案:Python头部、尾部和反向按行读取文本文件非常有前途,但它不适用于Python3.2,因为出现错误:
NameError: name 'file' is not defined

我之前尝试用File(TextIOWrapper)替换File(file),因为open()函数返回的是内置对象,但这导致了更多的错误(如果有人认为这是正确的方法,请告诉我,我可以详细解释一下:)


2
你可以查看 https://dev59.com/7XA65IYBdhLWcg3w6DFO 以获取几个解决方案,看看它们是否适合你的情况。 - Selcuk
请查看:https://dev59.com/0XVC5IYBdhLWcg3wixs0#260433 - Loïc G.
@LoïcG. 谢谢,这个解决方案看起来也很有前途,之前已经检查过了,但它也期望 file 而不是 TextIOWrapper,当传递 open('some_file.txt') 时也无法工作。 - Noam Inbar
请发布您的代码。您可以使用 with open(...) as file: ... - Loïc G.
2个回答

2

如果您不想阅读整个文件,可以使用 seek。以下是一个演示:

 $ cat words.txt 
foo
bar
baz
[6] oz123b@debian:~ $ ls -l words.txt 
-rw-r--r-- 1 oz123 oz123 12 Mar  9 19:38 words.txt

文件大小为12字节。您可以通过将光标向前移动8个字节来跳过到最后一个条目:
In [3]: w=open("words.txt")
In [4]: w.seek(8)
In [5]: w.readline()
Out[5]: 'baz\n'

为了完整回答,以下是如何将这些行反向打印出来的方法:
 w=open('words.txt')

In [6]: for s in [8, 4, 0]:
   ...:     _= w.seek(s)
   ...:     print(w.readline().strip())
   ...:     
baz
bar
foo

您需要探索文件的数据结构和每行的大小。我的示例非常简单,因为它旨在演示原理。


2
这是一个能够达到你所需功能的函数。
def reverse_lines(filename, BUFSIZE=4096):
    f = open(filename, "rb")
    f.seek(0, 2)
    p = f.tell()
    remainder = ""
    while True:
        sz = min(BUFSIZE, p)
        p -= sz
        f.seek(p)
        buf = f.read(sz) + remainder
        if '\n' not in buf:
            remainder = buf
        else:
            i = buf.index('\n')
            for L in buf[i+1:].split("\n")[::-1]:
                yield L
            remainder = buf[:i]
        if p == 0:
            break
    yield remainder

它的工作原理是从文件末尾(默认为4kb)读取缓冲区,并以相反的顺序生成其中的所有行。然后它向后移动4k并重复此过程,直到文件开头。如果正在处理的部分没有换行符(非常长的行),则代码可能需要在内存中保留超过4k的内容。

您可以使用以下代码:

for L in reverse_lines("my_big_file"):
   ... process L ...

谢谢!这个函数确实做到了我想要的。不过有一个小问题:使用“rb”打开导致错误: File "/home/noami/utils/doawasup.py", line 20, in reverse_lines buf = f.read(sz) + remainder TypeError: can't concat bytes to str 但是在去掉“b”之后,它完美地工作了。 - Noam Inbar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接