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