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





файлов построчно (9)


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

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 и generators см. В документации Python.

В Python, как я читаю в двоичном файле и перебираю каждый байт этого файла?




Чтение двоичного файла в 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 и т. Д.).




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)

Или, как говорит 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)



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

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

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




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




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

Или используйте решение JF Sebastians для улучшения скорости

from functools import partial

with open(filename, 'rb') as file:
    for byte in iter(partial(file.read, 1), b''):
        # Do stuff with byte

Или, если вы хотите, чтобы это была функция генератора, как показано в кодепере:

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)



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

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)

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




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

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

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




Для Python 2:

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = dict(x.items()+y.items())
print(z)

Для Python 3:

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = dict(x.items()|y.items())
print(z)

Он дает результат: {'a': 1, 'c': 11, 'b': 10}





python file-io binary