如何以惯用方式迭代二进制文件?

37

使用文本文件,我可以写出以下内容:

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个回答

40

尝试:

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)

太好了!谢谢。在保持相对高效的同时,放弃旧的习语(Perl)并学习新的习语确实很困难。 - dawg
@Lennart Regebro,@Jason Baker:我认为OP想要了解为什么他的iter调用没有起作用。在这种情况下,编写迭代器可能也是我会做的事情,除非在交互式提示符中工作。 - liori
@liori - 很好的观点。我错过了OP正在迭代f.read的事实。 - Jason Baker
16
functools.partial(f.read, numBytes)可以替代lambda的作用。 - Jochen Ritzel
5
哨兵应该是一个空的字节串,即 b''。在 Python 3 中,字符串文字是 Unicode 对象;而在 Python 2 中可以通过 from __future__ import unicode_literals 导入后,也可以使用 Unicode 字符串文字。 - George V. Reilly
显示剩余2条评论

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)

我原以为有一些内置的方法,我只是忽略了它。由于似乎没有内置的方法,所以这很容易阅读和直截了当。谢谢! - dawg

13

迭代读取二进制文件的Pythonic方式是使用内置函数 iter 的两个参数以及标准函数 functools.partial,如Python库文档中所述:

iter(object[, sentinel])

Return an iterator object. The first argument is interpreted very differently depending on the presence of the second argument. Without a second argument, object must be a collection object which supports the iteration protocol (the __iter__() method), or it must support the sequence protocol (the __getitem__() method with integer arguments starting at 0). If it does not support either of those protocols, TypeError is raised. If the second argument, sentinel, is given, then object must be a callable object. The iterator created in this case will call object with no arguments for each call to its __next__() method; if the value returned is equal to sentinel, StopIteration will be raised, otherwise the value will be returned.

See also Iterator Types.

One useful application of the second form of iter() is to build a block-reader. For example, reading fixed-width blocks from a binary database file until the end of file is reached:

from functools import partial

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

8

离这个问题已经快10年了,现在Python 3.8有了在PEP 572中描述的:=海象运算符。

要以Python 3.8或更高版本的方式惯用地和表达性地按块读取文件,可以这样做:

# A loop that cannot be trivially rewritten using 2-arg iter().
while chunk := file.read(1024 * 64):
    process(chunk)

我得到了一个语法错误:在“while chunk := input_file.read(1024 * 64):”这行代码中,有一个无效的语法。 - user1
你正在使用Python 3.8+吗? - dawg
为什么不能用2-art迭代轻松重写那个循环?其他答案似乎正是这样做的。 - Osman-pasha
我同意@Osman-pasha的观点,这是不真实的,或者至少可以用2个参数的iter()轻松重写。我想这取决于你所说的“轻松”。但我肯定同意这种方法更简单易读 - 而不是组合一个新函数(使用lambda或partial),然后将其作为回调传递给另一个函数来调用 - 这只是直接调用函数! - Arthur Tacca
我同意你的观点,并且我已经删除了评论。谢谢。 - undefined

-2
在Python 3.8+中,有一种新的赋值表达式:= - 被称为“海象运算符” - 可以将值分配给变量。有关更多详细信息,请参见PEP 572。因此,要按块读取文件,您可以执行以下操作:
def read_in_chunks(file_path, chunk_size=1024):
    with open(file_path, 'rb') as f:
        while chunk := f.read(chunk_size):
            yield chunk  # or process the chunk as desired

2
我认为这并没有添加任何之前答案中缺失的信息。 - Karl Knechtel
将近两年前:https://dev59.com/oG455IYBdhLWcg3wD_sB#61218911 - Thomas Weller

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