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