Каков идиоматический способ перебора двоичного файла?

С текстовым файлом я могу написать это:

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?


person dawg    schedule 30.12.2010    source источник


Ответы (4)


Я не знаю ни одного встроенного способа сделать это, но написать функцию-оболочку достаточно просто:

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)
person Jason Baker    schedule 30.12.2010
comment
Я предполагал, что есть какой-то встроенный способ, который я просто упускал из виду. Поскольку, кажется, нет встроенного способа, это легко читается и прямолинейно. Спасибо! - person dawg; 31.12.2010

Пытаться:

>>> with open('dups.txt','rb') as f:
...    for chunk in iter((lambda:f.read(how_many_bytes_you_want_each_time)),''):
...       i+=1

iter нужна функция без аргументов.

  • простой f.read будет читать весь файл, так как параметр size пропал, отсутствует;
  • f.read(1024) означает вызов функции и передачу ее возвращаемого значения (данные, загруженные из файла) в iter, поэтому iter вообще не получает функцию;
  • (lambda:f.read(1234)) — это функция, которая не принимает аргументов (ничего между lambda и :) и вызывает f.read(1234).

Существует эквивалентность между следующим:

somefunction = (lambda:f.read(how_many_bytes_you_want_each_time))

и

def somefunction(): return f.read(how_many_bytes_you_want_each_time)

и имея один из них перед вашим кодом, вы могли бы просто написать: iter(somefunction, '').

Технически вы можете пропустить круглые скобки вокруг лямбда, грамматика Python примет это.

person liori    schedule 30.12.2010
comment
Да, трюк со стражем с iter() действительно классный! (Хотя я не люблю лямбды, поэтому я бы сделал функцию). - person Lennart Regebro; 31.12.2010
comment
Это работает! Спасибо. Трудно потерять старые идиомы (Perl) и выучить новые, оставаясь при этом достаточно продуктивным. - person dawg; 31.12.2010
comment
Это работает... но, на мой взгляд, это немного сложно читать. - person Jason Baker; 31.12.2010
comment
@Lennart Regebro, @Jason Baker: Я предположил, что OP хочет узнать, почему его iter вызов не сработал. Написание итератора — это то, что я, вероятно, также сделал бы в этом случае, если бы не работал в интерактивной подсказке. - person liori; 31.12.2010
comment
@liori - Хороший вопрос. Я пропустил тот факт, что ОП перебирал f.read. - person Jason Baker; 31.12.2010
comment
functools.partial(f.read, numBytes) тоже должно работать вместо lambda - person Jochen Ritzel; 31.12.2010
comment
Sentinel должен быть пустой строкой байтов, b''. Строковые литералы — это объекты Unicode в Python 3 или с from __future__ import unicode_literals в Python 2. - person George V. Reilly; 01.12.2016

Pythonic способ итеративного чтения двоичного файла использует встроенную функцию iter с двумя аргументами и стандартную функцию functools.partial, как описано в документация библиотеки Python:

iter(объект[, страж])

Возвратите объект итератора. Первый аргумент интерпретируется очень по-разному в зависимости от наличия второго аргумента. Без второго аргумента object должен быть объектом коллекции, который поддерживает протокол итерации (метод __iter__()) или должен поддерживать протокол последовательности (метод __getitem__() с целочисленными аргументами, начинающимися с 0). Если он не поддерживает ни один из этих протоколов, поднимается TypeError. Если задан второй аргумент, sentinel, то object должен быть вызываемым объектом. Итератор, созданный в этом случае, будет вызывать object без аргументов для каждого вызова его __next__() метода; если возвращаемое значение равно sentinel, 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)
person Maggyero    schedule 04.09.2019

Спустя почти 10 лет после этого вопроса, и теперь в Python 3.8 есть := оператор Walrus, описанный в PEP 572.

Чтобы прочитать файл по частям идиоматически и выразительно (с Python 3.8 или более поздней версии), вы можете сделать:

# A loop that cannot be trivially rewritten using 2-arg iter().
while chunk := file.read(1024 * 64):
    process(chunk)
person dawg    schedule 14.04.2020
comment
я получил while chunk := input_file.read(1024 * 64): ^ SyntaxError: неверный синтаксис - person user1; 10.02.2021
comment
Вы используете Python 3.8+? - person dawg; 22.02.2021