Итератор файла Python по двоичному файлу с более новой идиомой

В Python для двоичного файла я могу написать это:

buf_size=1024*64           # this is an important size...
with open(file, "rb") as f:
   while True:
      data=f.read(buf_size)
      if not data: break
      # deal with the data....

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

with open(file, "r") as file:
   for line in file:
       # deal with each line....

Это сокращение для:

with open(file, "r") as file:
   for line in iter(file.readline, ""):
       # deal with each line....

Эта идиома зарегистрирована в PEP 234, но мне не удалось найти аналогичную идиому для двоичных файлов.

Я пробовал это:

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

>>> i
1                # 30 MB file, i==1 means read in one go...

Я попытался поставить iter(f.read(buf_size),''), но это синтаксическая ошибка из-за паренс после вызываемого в iter().

Я знаю, что могу написать функцию, но есть ли способ с идиомой по умолчанию for chunk in file:, где я могу использовать размер буфера в сравнении с ориентированной линией?

Спасибо, что поделился с новичком Python, который пытался написать свой первый нетривиальный и идиоматический Python script.

Ответ 1

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

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 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)

Ответ 2

Try:

>>> 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, '').

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

Ответ 3

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

iter(object[, sentinel])

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