Чтение двоичного файла и цикл по каждому байту

В 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.