Чтение двоичного файла и цикл по каждому байту - PullRequest
324 голосов
/ 24 июня 2009

Как в Python читать в двоичном файле и перебирать каждый байт этого файла?

Ответы [ 10 ]

335 голосов
/ 24 июня 2009

Python 2.4 и ранее

f = open("myfile", "rb")
try:
    byte = f.read(1)
    while byte != "":
        # Do stuff with byte.
        byte = f.read(1)
finally:
    f.close()

Python 2,5-2,7

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

В Python 3 все немного по-другому. Мы больше не будем получать необработанные символы из потока в байтовом режиме, а только байтовые объекты, поэтому нам нужно изменить условие:

with open("myfile", "rb") as f:
    byte = f.read(1)
    while byte != b"":
        # Do stuff with byte.
        byte = f.read(1)

Или, как говорит Бенхойт, пропустите неравное и воспользуйтесь тем, что b"" оценивается как ложное. Это делает код совместимым между 2.6 и 3.x без каких-либо изменений. Это также избавит вас от изменения условия, если вы перейдете из байтового режима в текстовый или наоборот.

with open("myfile", "rb") as f:
    byte = f.read(1)
    while byte:
        # Do stuff with byte.
        byte = f.read(1)
157 голосов
/ 24 июня 2009

Этот генератор возвращает байты из файла, читая файл кусками:

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)

Информацию об итераторах и генераторах .

см. В документации Python.
47 голосов
/ 24 июня 2009

Если файл не слишком большой, проблема заключается в том, что удержание его в памяти является проблемой:

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()
31 голосов
/ 16 ноября 2013

Для чтения файла - по одному байту за раз (без учета буферизации) - вы можете использовать встроенную функцию с двумя аргументами 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'' (пустая строка). Память не увеличивается неограниченно для больших файлов. Вы можете передать buffering=0 в open(), чтобы отключить буферизацию - это гарантирует, что только один байт читается за итерацию (медленно).

with -соглашение автоматически закрывает файл - включая случай, когда приведенный ниже код вызывает исключение.

Несмотря на наличие внутренней буферизации по умолчанию, по-прежнему неэффективно обрабатывать по одному байту за раз. Например, вот утилита 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 ГБ / с при chunksize == 32768 на моей машине и только ~ 7,5 МБ / с при 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 на порядок быстрее.

19 голосов
/ 06 сентября 2013

Подводя итог всем блестящим моментам Крисси, Скурмеделя, Бена Хойта и Питера Хансена, это было бы оптимальным решением для обработки двоичного файла по одному байту за раз:

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-буферы внутри - нет необходимости читать фрагменты
  • СУХОЙ принцип - не повторять строку чтения
  • с оператором обеспечивает чистое закрытие файла
  • 'byte' оценивается как false, когда байтов больше нет (не, если байт равен нулю)

Или используйте раствор Дж. Ф. Себастьяна для улучшения скорости

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)
16 голосов
/ 14 мая 2016

Чтение двоичного файла в 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 позволяет нам избежать блокировок, и из-за этого он может вернуться быстрее. Другие ответы также не упоминают об этом.

Демонстрация использования лучших практик:

Давайте создадим файл с мегабайтом (фактически, мегибайтом) псевдослучайных данных:

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, латинице и т. Д.)

6 голосов
/ 25 сентября 2016

Python 3, прочитать все файлы сразу:

with open("filename", "rb") as binary_file:
    # Read the whole file at once
    data = binary_file.read()
    print(data)

Вы можете перебирать все что угодно, используя переменную data.

4 голосов
/ 19 марта 2019

Попробовав все вышеперечисленное и воспользовавшись ответом @Aaron Hall, я получил ошибки памяти для файла размером ~ 90 МБ на компьютере под управлением Windows 10, 8 ГБ ОЗУ и 32-разрядной версии Python 3.5. Коллега порекомендовал мне использовать numpy, и это творит чудеса.

На данный момент самым быстрым для чтения всего двоичного файла (который я тестировал) является:

import numpy as np

file = "binary_file.bin"
data = np.fromfile(file, 'u1')

Ссылка

Множество людей быстрее, чем любые другие методы. Надеюсь, это кому-нибудь поможет!

4 голосов
/ 01 июля 2015

Если у вас есть много двоичных данных для чтения, вы можете рассмотреть struct module . Это задокументировано как преобразование «между типами C и Python», но, конечно, байты являются байтами, и не имеет значения, были ли они созданы как типы C. Например, если ваши двоичные данные содержат два 2-байтовых целых и одно 4-байтовое целое, вы можете прочитать их следующим образом (пример взят из struct документации):

>>> struct.unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')
(1, 2, 3)

Это может оказаться более удобным, быстрым или иным, чем явным зацикливанием содержимого файла.

2 голосов
/ 22 сентября 2015

Если вы ищете что-то быстрое, вот метод, который я использовал, который работал годами:

from array import array

with open( path, 'rb' ) as file:
    data = array( 'B', file.read() ) # buffer the file

# evaluate it's data
for byte in data:
    v = byte # int value
    c = chr(byte)

если вы хотите итерировать символы вместо целых, вы можете просто использовать data = file.read(), который должен быть объектом bytes () в py3.

...