Итератор файла 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.

5 ответов

Решение

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

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)

Пытаться:

>>> 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 примет это.

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

iter(объект[, дозорный])

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

Спустя почти 10 лет после этого вопроса, Python 3.8 имеет := Оператор моржа описан в PEP 572.

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

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

Помимо того, что его легче писать и читать, он также несколько быстрее, чем использование iter поскольку при каждом чтении фрагмента избегается один вызов функции Python.

В 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
Другие вопросы по тегам