Вопрос по file-io, python – Чтение двоичного файла и цикл по каждому байту

312

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

Ваш Ответ

9   ответов
3

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

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

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

18

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

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 buffers internally - no need to read chunks DRY principle - do not repeat the read line with statement ensures a clean file close 'byte' evaluates to false when there are no more bytes (not when a byte is zero)

Или используйте решение 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)
Error: User Rate Limit Exceeded
30

по одному байту за раз (без учета буферизации) & # x2014; Вы могли бы использоватьдва-аргументiter(callable, sentinel) built-in function:

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(), чтобы отключить буферизацию & # x2014; это гарантирует, что только один байт читается за итерацию (медленно).

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

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

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 на порядок быстрее.

Error: User Rate Limit ExceedednumpyError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceedednumpy.memmap()Error: User Rate Limit Exceeded
1

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

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.

Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
149

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 для получения информации оитераторы а такжегенераторы.

Error: User Rate Limit ExceededBytesError: User Rate Limit Exceeded8192 Byte = 8 kBError: User Rate Limit ExceededKiBError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceededfor b in chunk:Error: User Rate Limit Exceededyield from chunkError: User Rate Limit ExceededyieldError: User Rate Limit ExceededYield Expressions).
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
322

Python 2.4 and Earlier

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)
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceededfor the code I've tried.
14
Reading binary file in Python and looping over each byte

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)

(Несколько других ответов упоминают об этом, но немногие предлагают разумный размер чтения.)

Best practice for large files or buffered/interactive reading

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

Demonstration of best practice usage:

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

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]
Don't iterate by lines for binary files

Не делайте следующее - это тянет кусок произвольного размера, пока не дойдет до символа новой строки - слишком медленно, когда чанки слишком малы и, возможно, слишком велики:

    with open(path, 'rb') as file:
        for chunk in file: # text newline iteration - not for bytes
            for byte in chunk:
                yield byte

Вышесказанное подходит только для того, что является семантически читаемыми текстовыми файлами (такими как простой текст, код, разметка, разметка и т. Д., По существу, все, что закодировано в ascii, utf, латинице и т. Д.)

Error: User Rate Limit ExceededPathError: User Rate Limit ExceededopenError: User Rate Limit ExceededPathError: User Rate Limit Exceeded
Error: User Rate Limit Exceededfile_byte_iteratorError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceededpath = Path(path), with path.open('rb') as file:Error: User Rate Limit Exceeded
4

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

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

47

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

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()
Error: User Rate Limit ExceededwithError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit ExceededcloseError: User Rate Limit Exceededbytes_read = open("filename", "rb").read()Error: User Rate Limit Exceeded

Похожие вопросы