В Python, как я читаю в двоичном файле и петлю над каждым байтом этого файла?
Чтение двоичного файла и цикл по каждому байту
Ответ 1
f = open("myfile", "rb")
try:
byte = f.read(1)
while byte != "":
# Do stuff with byte.
byte = f.read(1)
finally:
f.close()
По предложению chrispy:
with open("myfile", "rb") as f:
byte = f.read(1)
while byte != "":
# Do stuff with byte.
byte = f.read(1)
Обратите внимание, что оператор with не доступен в версиях Python ниже 2.5. Чтобы использовать его в версии 2.5, вам необходимо импортировать его:
from __future__ import with_statement
В 2.6 это не требуется.
В Python 3 это немного отличается. Мы больше не будем получать сырые символы из потока в байтовом режиме, кроме байт-объектов, поэтому нам нужно изменить условие:
with open("myfile", "rb") as f:
byte = f.read(1)
while byte != b"":
# Do stuff with byte.
byte = f.read(1)
Или, как говорит benhoyt, пропустите не равное и воспользуйтесь тем, что b""
оценивается как false. Это делает код совместимым между 2.6 и 3.x без каких-либо изменений. Это также избавит вас от изменения условия, если вы переходите из режима байта в текст или наоборот.
with open("myfile", "rb") as f:
byte = f.read(1)
while byte:
# Do stuff with byte.
byte = f.read(1)
Ответ 2
Этот генератор дает байты из файла, считывая файл в кусках:
def bytes_from_file(filename, chunksize=8192):
with open(filename, "rb") as f:
while True:
chunk = f.read(chunksize)
if chunk:
for b in chunk:
yield b
else:
break
# example:
for b in bytes_from_file('filename'):
do_stuff_with(b)
Информацию о iterators и .
Ответ 3
Если файл не слишком большой, что проблема с хранением в памяти - это проблема:
bytes_read = open("filename", "rb").read()
for b in bytes_read:
process_byte(b)
где process_byte представляет некоторую операцию, которую вы хотите выполнить на байте с передачей.
Если вы хотите обработать фрагмент за раз:
file = open("filename", "rb")
try:
bytes_read = file.read(CHUNKSIZE)
while bytes_read:
for b in bytes_read:
process_byte(b)
bytes_read = file.read(CHUNKSIZE)
finally:
file.close()
Ответ 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
на порядок быстрее.
Ответ 5
Подводя итог всем блестящим пунктам Chrispy, Skurmedel, Ben Hoyt и Peter Hansen, это было бы оптимальным решением для обработки двоичного файла по одному байту за раз:
with open("myfile", "rb") as f:
while True:
byte = f.read(1)
if not byte:
break
do_stuff_with(ord(byte))
Для версий python 2.6 и выше, потому что:
- буферы python внутри - не нужно читать фрагменты
- Принцип DRY - не повторяйте строку чтения
- с инструкцией обеспечивает закрытие файла
- 'byte' вычисляет значение false, когда больше нет байтов (не тогда, когда байт равен нулю)
Или используйте решение J. F. Sebastians для улучшения скорости
from functools import partial
with open(filename, 'rb') as file:
for byte in iter(partial(file.read, 1), b''):
# Do stuff with byte
Или, если вы хотите, чтобы это была функция генератора, показанная с помощью codeape:
def bytes_from_file(filename):
with open(filename, "rb") as f:
while True:
byte = f.read(1)
if not byte:
break
yield(ord(byte))
# example:
for b in bytes_from_file('filename'):
do_stuff_with(b)
Ответ 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 и т.д.).
Ответ 7
Python 3, сразу прочитайте весь файл:
with open("filename", "rb") as binary_file:
# Read the whole file at once
data = binary_file.read()
print(data)
Вы можете перебирать все, что хотите, используя переменную data
.
Ответ 8
Если у вас есть много двоичных данных для чтения, вам может потребоваться рассмотреть struct module. Он документируется как преобразование "между типами C и Python", но, конечно, байты являются байтами, и независимо от того, были ли они созданы как типы C, не имеет значения. Например, если ваши двоичные данные содержат два целых 2 байта и одно целое число из 4 байтов, вы можете прочитать их следующим образом (пример взято из документации struct
):
>>> struct.unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')
(1, 2, 3)
Вы можете найти это более удобным, быстрым или и тем, и другим, чем явным образом перебирать содержимое файла.
Ответ 9
если вы ищете что-то быстрое, здесь метод, который я использовал, работал много лет:
from array import array
with open( path, 'rb' ) as file:
data = array( 'B', file.read() ) # buffer the file
# evaluate it data
for byte in data:
v = byte # int value
c = chr(byte)
если вы хотите итерации символов вместо ints, вы можете просто использовать data = file.read()
, который должен быть объектом bytes() в py3.