Ответ 4
Для чтения файла - по одному байту за раз (без учета буферизации) - вы можете использовать встроенную функцию two-argument iter(callable, sentinel)
:
with open(filename, 'rb') as file:
for byte in iter(lambda: file.read(1), b''):
# Do stuff with byte
Он вызывает file.read(1)
, пока не вернет ничего b''
(пустая bytestring). Для больших файлов память не увеличивается неограниченно. Вы можете передать buffering=0
в open()
, чтобы отключить буферизацию - это гарантирует, что на каждую итерацию (медленную) считывается только один байт.
with
-statement автоматически закрывает файл - включая случай, когда код под ним вызывает исключение.
Несмотря на наличие внутренней буферизации по умолчанию, все равно неэффективно обрабатывать один байт за раз. Например, здесь утилита blackhole.py
, которая ест все, что ей дано:
#!/usr/bin/env python3
"""Discard all input. `cat > /dev/null` analog."""
import sys
from functools import partial
from collections import deque
chunksize = int(sys.argv[1]) if len(sys.argv) > 1 else (1 << 15)
deque(iter(partial(sys.stdin.detach().read, chunksize), b''), maxlen=0)
Пример:
$ dd if=/dev/zero bs=1M count=1000 | python3 blackhole.py
Он обрабатывает ~ 1.5 GB/s, когда chunksize == 32768
на моей машине и только ~ 7.5 MB/s, когда chunksize == 1
. То есть, в 200 раз медленнее читать по одному байту за раз. Учитывайте это, если вы можете переписать свою обработку для использования более одного байта за раз, и если вам нужна производительность.
mmap
позволяет обрабатывать файл как bytearray
и объект файла одновременно. Он может служить альтернативой загрузке всего файла в память, если вам нужен доступ к обоим интерфейсам. В частности, вы можете перебирать один байт за один раз над файлом с отображением памяти, используя простой for
-loop:
from mmap import ACCESS_READ, mmap
with open(filename, 'rb', 0) as f, mmap(f.fileno(), 0, access=ACCESS_READ) as s:
for byte in s: # length is equal to the current file size
# Do stuff with byte
mmap
поддерживает нотацию среза. Например, mm[i:i+len]
возвращает len
байты из файла, начинающегося с позиции i
. Протокол менеджера контекста не поддерживается до Python 3.2; вам нужно явно вызвать mm.close()
в этом случае. Итерация по каждому байту с использованием mmap
потребляет больше памяти, чем file.read(1)
, но mmap
на порядок быстрее.
Ответ 6
Чтение двоичного файла в Python и цикл по каждому байту
Новый в Python 3.5 является модулем pathlib
, который имеет метод удобства, специально предназначенный для чтения в файле в виде байтов, что позволяет нам перебирать байты. Я считаю, что это достойный (если быстрый и грязный) ответ:
import pathlib
for byte in pathlib.Path(path).read_bytes():
print(byte)
Интересно, что это единственный ответ, чтобы упомянуть pathlib
.
В Python 2 вы, вероятно, сделаете это (как предлагает Vinay Sajip):
with open(path, 'b') as file:
for byte in file.read():
print(byte)
В случае, если файл может быть слишком большим, чтобы перебирать его в памяти, вы должны его обманывать, идиоматически, используя функцию iter
с подписью callable, sentinel
- версию Python 2:
with open(path, 'b') as file:
callable = lambda: file.read(1024)
sentinel = bytes() # or b''
for chunk in iter(callable, sentinel):
for byte in chunk:
print(byte)
(Несколько других ответов упоминают об этом, но немногие предлагают разумный размер чтения.)
Лучшая практика для больших файлов или буферизованное/интерактивное чтение
Позвольте создать функцию для этого, включая идиоматические использования стандартной библиотеки для Python 3.5 +:
from pathlib import Path
from functools import partial
from io import DEFAULT_BUFFER_SIZE
def file_byte_iterator(path):
"""given a path, return an iterator over the file
that lazily loads the file
"""
path = Path(path)
with path.open('rb') as file:
reader = partial(file.read1, DEFAULT_BUFFER_SIZE)
file_iterator = iter(reader, bytes())
for chunk in file_iterator:
for byte in chunk:
yield byte
Обратите внимание, что мы используем file.read1
. file.read
блокирует, пока не получит все запрошенные байты, или EOF
. file.read1
позволяет избежать блокировки, и из-за этого он может быстрее вернуться. Никакие другие ответы не упоминают об этом.
Демонстрация использования наилучшей практики:
Сделайте файл с мегабайтом (фактически mebibyte) псевдослучайных данных:
import random
import pathlib
path = 'pseudorandom_bytes'
pathobj = pathlib.Path(path)
pathobj.write_bytes(
bytes(random.randint(0, 255) for _ in range(2**20)))
Теперь перейдем к ней и материализуем ее в памяти:
>>> l = list(file_byte_iterator(path))
>>> len(l)
1048576
Мы можем проверять любую часть данных, например, последние 100 и первые 100 байтов:
>>> l[-100:]
[208, 5, 156, 186, 58, 107, 24, 12, 75, 15, 1, 252, 216, 183, 235, 6, 136, 50, 222, 218, 7, 65, 234, 129, 240, 195, 165, 215, 245, 201, 222, 95, 87, 71, 232, 235, 36, 224, 190, 185, 12, 40, 131, 54, 79, 93, 210, 6, 154, 184, 82, 222, 80, 141, 117, 110, 254, 82, 29, 166, 91, 42, 232, 72, 231, 235, 33, 180, 238, 29, 61, 250, 38, 86, 120, 38, 49, 141, 17, 190, 191, 107, 95, 223, 222, 162, 116, 153, 232, 85, 100, 97, 41, 61, 219, 233, 237, 55, 246, 181]
>>> l[:100]
[28, 172, 79, 126, 36, 99, 103, 191, 146, 225, 24, 48, 113, 187, 48, 185, 31, 142, 216, 187, 27, 146, 215, 61, 111, 218, 171, 4, 160, 250, 110, 51, 128, 106, 3, 10, 116, 123, 128, 31, 73, 152, 58, 49, 184, 223, 17, 176, 166, 195, 6, 35, 206, 206, 39, 231, 89, 249, 21, 112, 168, 4, 88, 169, 215, 132, 255, 168, 129, 127, 60, 252, 244, 160, 80, 155, 246, 147, 234, 227, 157, 137, 101, 84, 115, 103, 77, 44, 84, 134, 140, 77, 224, 176, 242, 254, 171, 115, 193, 29]
Не выполнять итерацию по строкам для двоичных файлов
Не выполняйте следующее: это вытаскивает кусок произвольного размера, пока не дойдет до символа новой строки - слишком медленно, когда куски слишком малы и, возможно, слишком большие:
with open(path, 'rb') as file:
for chunk in file: # text newline iteration - not for bytes
for byte in chunk:
yield byte
Вышеприведенное только полезно для семантически понятных для чтения текстовых файлов (таких как простой текст, код, разметка, уценка и т.д.... по существу, любые ascii, utf, latin и т.д.).