遍历二进制文件的惯用方法是什么?

38 投票
5 回答
27581 浏览
提问于 2025-04-16 09:13

对于一个文本文件,我可以这样写:

with open(path, 'r') as file:
    for line in file:
        # handle the line

这和下面这个是一样的:

with open(path, 'r') as file:
    for line in iter(file.readline, ''):
        # handle the line

这个写法在 PEP 234 中有说明,但我找不到类似的写法适用于二进制文件。

对于一个二进制文件,我可以这样写:

with open(path, 'rb') as file:
    while True:
        chunk = file.read(1024 * 64)
        if not chunk:
            break
        # handle the chunk

我尝试用和文本文件一样的写法:

def make_read(file, size):
    def read():
        return file.read(size)
    return read

with open(path, 'rb') as file:
    for chunk in iter(make_read(file, 1024 * 64), b''):
        # handle the chunk

在Python中,遍历二进制文件的这种写法是对的吗?

5 个回答

13

在Python中,逐步读取二进制文件的一个很好的方法是使用内置的 iter 函数,配合两个参数,以及标准库中的 functools.partial,具体可以参考Python文档

iter(对象[, 哨兵])

这个函数会返回一个迭代器对象。第一个参数的含义会根据第二个参数是否存在而有所不同。如果没有第二个参数,对象必须是一个支持迭代的集合(也就是有 __iter__() 方法的对象),或者是一个支持序列协议的对象(也就是可以用整数从 0 开始索引的对象)。如果不满足这两种情况,就会报 TypeError 错误。如果提供了第二个参数 哨兵,那么 对象 必须是一个可调用的对象。在这种情况下,创建的迭代器会在每次调用 __next__() 方法时调用 对象,如果返回的值等于 哨兵,就会抛出 StopIteration,否则就返回这个值。

你也可以查看迭代器类型的相关内容。

使用 iter() 的第二种形式的一个实用例子是构建一个块读取器。例如,从一个二进制数据库文件中读取固定宽度的块,直到文件结束:

from functools import partial

with open('mydata.db', 'rb') as f:
    for block in iter(partial(f.read, 64), b''):
        process_block(block)
41

试试这个:

chunk_size = 4 * 1024 * 1024  # MB

with open('large_file.dat','rb') as f:
    for chunk in iter(lambda: f.read(chunk_size), b''):
        handle(chunk)

iter 需要一个没有参数的函数。

  • 直接用 f.read 会把整个文件都读出来,因为没有指定 size 参数;
  • f.read(1024) 这行代码的意思是调用一个函数,并把它返回的值(从文件中加载的数据)传给 iter,所以 iter 根本没有得到一个函数;
  • (lambda:f.read(1234)) 是一个不带参数的函数(在 lambda: 之间没有东西),它会调用 f.read(1234)
25

我不知道有没有现成的方法可以做到这一点,不过写一个包装函数其实很简单:

def read_in_chunks(infile, chunk_size=1024*64):
    while True:
        chunk = infile.read(chunk_size)
        if chunk:
            yield chunk
        else:
            # The chunk was empty, which means we're at the end
            # of the file
            return

然后在交互式提示符下:

>>> from chunks import read_in_chunks
>>> infile = open('quicklisp.lisp')
>>> for chunk in read_in_chunks(infile):
...     print chunk
... 
<contents of quicklisp.lisp in chunks>

当然,你也可以很容易地把这个改成用 with 语句块:

with open('quicklisp.lisp') as infile:
    for chunk in read_in_chunks(infile):
        print chunk

而且你可以像这样去掉 if 语句。

def read_in_chunks(infile, chunk_size=1024*64):
    chunk = infile.read(chunk_size)
    while chunk:
        yield chunk
        chunk = infile.read(chunk_size)

撰写回答