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

377

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

Джесси Фогт
источник

Ответы:

387

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)

Python 3.8

Отныне благодаря оператору: = приведенный выше код может быть написан более коротким способом.

with open("myfile", "rb") as f:
    while (byte := f.read(1)):
        # Do stuff with byte.
Skurmedel
источник
40
Чтение файла побайтно - это кошмар производительности. Это не может быть лучшим решением, доступным в Python. Этот код следует использовать с осторожностью.
USR
7
@usr: Хорошо, файловые объекты буферизуются внутри, и даже в этом случае это то, о чем просили. Не каждый сценарий требует оптимальной производительности.
Скурмедель
4
@mezhaka: Таким образом, вы изменяете его с read (1) на read (bufsize) и в цикле while вы делаете for-in ... пример остается в силе.
Skurmedel
3
@usr: разница в производительности для кода, который я пробовал, может достигать 200 раз .
JFS
2
@usr - это зависит от того, сколько байтов вы хотите обработать. Если их достаточно мало, «плохо» работающий, но легко понятный код может оказаться более предпочтительным. Трата циклов ЦП компенсируется сохранением «циклов ЦП считывателя» при обслуживании кода.
IllvilJa
172

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

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

codeape
источник
3
@codeape Как раз то, что я ищу. Но как определить размер кусочка? Это может быть произвольное значение?
swdev
3
@swdev: в примере используется размер фрагмента 8192 байта . Параметр для функции file.read () - просто указывает размер, то есть количество байтов, которые нужно прочитать. Codeape выбрал 8192 Byte = 8 kB(на самом деле это, KiBно это не так широко известно). Значение является «полностью» случайным, но 8 кБ, по-видимому, является подходящим значением: не слишком много памяти тратится впустую, и все же не существует «слишком много» операций чтения, как в принятом ответе Скурмеделя ...
mozzbozz
3
Файловая система уже буферизует порции данных, поэтому этот код является избыточным. Лучше читать байт за раз.
Суровая
17
Хотя это уже быстрее, чем принятый ответ, это можно ускорить еще на 20-25%, заменив весь самый внутренний for b in chunk:цикл на yield from chunk. Эта форма yieldбыла добавлена ​​в Python 3.3 (см. Выражения доходности ).
Мартино
3
Хм кажется маловероятным, ссылка?
Codeape
54

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

with open("filename", "rb") as f:
    bytes_read = f.read()
for b in bytes_read:
    process_byte(b)

где process_byte представляет некоторую операцию, которую вы хотите выполнить над переданным байтом.

Если вы хотите обрабатывать чанк одновременно:

with open("filename", "rb") as f:
    bytes_read = f.read(CHUNKSIZE)
    while bytes_read:
        for b in bytes_read:
            process_byte(b)
        bytes_read = f.read(CHUNKSIZE)

withОтчет доступен в Python 2.5 и выше.

Винай Саджип
источник
1
Возможно, вас заинтересует тест, который я только что опубликовал.
Мартино
37

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

JFS
источник
Я нашел последний пример очень интересным. Жаль, что нет эквивалентных numpy(байтовых) массивов с отображением в памяти.
Мартино
1
@martineau есть, numpy.memmap()и вы можете получать данные по одному байту за раз (ctypes.data). Вы можете думать о пустых массивах как о чем-то большем, чем о каплях в памяти + метаданных.
JFS
JFS: Спасибо, отличные новости! Не знал, что такое существует. Отличный ответ, кстати.
Мартино
25

Чтение двоичного файла в 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:
            yield from chunk

Обратите внимание, что мы используем 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
            yield from chunk

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

Аарон Холл
источник
2
Это НАМНОГО лучше ... спасибо за это. Я знаю, что не всегда весело возвращаться к двухлетнему ответу, но я ценю, что вы это сделали. Мне особенно нравится подзаголовок «Не повторять по строкам» :-)
Floris
1
Привет, Аарон, есть ли причина, почему вы решили использовать path = Path(path), with path.open('rb') as file:вместо того, чтобы вместо этого использовать встроенную функцию открытия? Они оба делают одно и то же правильно?
Джошуа Йонатан
1
@JoshuaYonathan Я использую Pathобъект, потому что это очень удобный новый способ обработки путей. Вместо того, чтобы передавать строку в тщательно выбранные «правильные» функции, мы можем просто вызывать методы объекта path, который, по сути, содержит большую часть важной функциональности, которую вы хотите, с семантически строкой пути. С IDE, которые могут проверять, мы можем легче получить автозаполнение. Мы могли бы сделать то же самое с помощью openвстроенной функции, но при написании программы у программиста есть множество положительных сторон для использования этого Pathобъекта.
Аарон Холл
1
Последний метод, который вы упомянули, используя функцию, file_byte_iteratorнамного быстрее, чем все методы, которые я пробовал на этой странице. Слава тебе!
Рик М.
@RickM: Вас может заинтересовать тест, который я только что опубликовал.
Мартино
19

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

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

Или, если вы хотите, чтобы это была функция генератора, как продемонстрировано 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)
Хольгер Билле
источник
2
Как сказано в связанном ответе, чтение / обработка по одному байту за раз все еще медленны в Python, даже если чтения буферизуются. Производительность может быть значительно улучшена, если одновременно обрабатывать несколько байтов, как в примере в связанном ответе: 1,5 ГБ / с против 7,5 МБ / с.
JFS
6

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

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

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

Мирча
источник
6

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

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

import numpy as np

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

Ссылка

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

Рик М.
источник
3
Хорошо, но не может использоваться для двоичного файла, содержащего разные типы данных.
Нирмал
@Nirmal: Вопрос о циклическом переходе по байту, поэтому неясно, имеет ли какое-либо отношение ваш комментарий о разных типах данных.
Мартино
1
Рик: Ваш код не делает то же самое, что и другие - а именно, циклически обрабатывая каждый байт. Если это добавить к этому, то он не будет быстрее, чем большинство других, по крайней мере, согласно результатам в моем тесте . На самом деле это, кажется, один из более медленных подходов. Если обработка, выполняемая для каждого байта (независимо от того, что это может быть), является чем-то, что может быть выполнено с помощью numpy, тогда может быть целесообразно.
Мартино
@martineau Спасибо за ваши комментарии, да, я понимаю, что вопрос в том, чтобы циклически проходить каждый байт, а не просто загружать все за один раз, но есть и другие ответы на этот вопрос, которые также указывают на чтение всего содержимого и, следовательно, мой ответ
Рик М.
4

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

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

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

Геррит
источник
4

Этот пост сам по себе не является прямым ответом на вопрос. Вместо этого это расширяемый эталон на основе данных, который можно использовать для сравнения многих ответов (и вариантов использования новых функций, добавленных в более поздние, более современные версии Python), которые были опубликованы на этот вопрос - и поэтому должны быть полезным в определении того, у кого лучшая производительность.

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

Во-первых, вот результаты последних версий Python 2 и 3:

Fastest to slowest execution speeds with 32-bit Python 2.7.16
  numpy version 1.16.5
  Test file size: 1,024 KiB
  100 executions, best of 3 repetitions

1                  Tcll (array.array) :   3.8943 secs, rel speed   1.00x,   0.00% slower (262.95 KiB/sec)
2  Vinay Sajip (read all into memory) :   4.1164 secs, rel speed   1.06x,   5.71% slower (248.76 KiB/sec)
3            codeape + iter + partial :   4.1616 secs, rel speed   1.07x,   6.87% slower (246.06 KiB/sec)
4                             codeape :   4.1889 secs, rel speed   1.08x,   7.57% slower (244.46 KiB/sec)
5               Vinay Sajip (chunked) :   4.1977 secs, rel speed   1.08x,   7.79% slower (243.94 KiB/sec)
6           Aaron Hall (Py 2 version) :   4.2417 secs, rel speed   1.09x,   8.92% slower (241.41 KiB/sec)
7                     gerrit (struct) :   4.2561 secs, rel speed   1.09x,   9.29% slower (240.59 KiB/sec)
8                     Rick M. (numpy) :   8.1398 secs, rel speed   2.09x, 109.02% slower (125.80 KiB/sec)
9                           Skurmedel :  31.3264 secs, rel speed   8.04x, 704.42% slower ( 32.69 KiB/sec)

Benchmark runtime (min:sec) - 03:26

Fastest to slowest execution speeds with 32-bit Python 3.8.0
  numpy version 1.17.4
  Test file size: 1,024 KiB
  100 executions, best of 3 repetitions

1  Vinay Sajip + "yield from" + "walrus operator" :   3.5235 secs, rel speed   1.00x,   0.00% slower (290.62 KiB/sec)
2                       Aaron Hall + "yield from" :   3.5284 secs, rel speed   1.00x,   0.14% slower (290.22 KiB/sec)
3         codeape + iter + partial + "yield from" :   3.5303 secs, rel speed   1.00x,   0.19% slower (290.06 KiB/sec)
4                      Vinay Sajip + "yield from" :   3.5312 secs, rel speed   1.00x,   0.22% slower (289.99 KiB/sec)
5      codeape + "yield from" + "walrus operator" :   3.5370 secs, rel speed   1.00x,   0.38% slower (289.51 KiB/sec)
6                          codeape + "yield from" :   3.5390 secs, rel speed   1.00x,   0.44% slower (289.35 KiB/sec)
7                                      jfs (mmap) :   4.0612 secs, rel speed   1.15x,  15.26% slower (252.14 KiB/sec)
8              Vinay Sajip (read all into memory) :   4.5948 secs, rel speed   1.30x,  30.40% slower (222.86 KiB/sec)
9                        codeape + iter + partial :   4.5994 secs, rel speed   1.31x,  30.54% slower (222.64 KiB/sec)
10                                        codeape :   4.5995 secs, rel speed   1.31x,  30.54% slower (222.63 KiB/sec)
11                          Vinay Sajip (chunked) :   4.6110 secs, rel speed   1.31x,  30.87% slower (222.08 KiB/sec)
12                      Aaron Hall (Py 2 version) :   4.6292 secs, rel speed   1.31x,  31.38% slower (221.20 KiB/sec)
13                             Tcll (array.array) :   4.8627 secs, rel speed   1.38x,  38.01% slower (210.58 KiB/sec)
14                                gerrit (struct) :   5.0816 secs, rel speed   1.44x,  44.22% slower (201.51 KiB/sec)
15                 Rick M. (numpy) + "yield from" :  11.8084 secs, rel speed   3.35x, 235.13% slower ( 86.72 KiB/sec)
16                                      Skurmedel :  11.8806 secs, rel speed   3.37x, 237.18% slower ( 86.19 KiB/sec)
17                                Rick M. (numpy) :  13.3860 secs, rel speed   3.80x, 279.91% slower ( 76.50 KiB/sec)

Benchmark runtime (min:sec) - 04:47

Я также запустил его с гораздо большим тестовым файлом 10 МБ (который потребовался почти час) и получил результаты производительности, которые были сопоставимы с показанными выше.

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

from __future__ import print_function
import array
import atexit
from collections import deque, namedtuple
import io
from mmap import ACCESS_READ, mmap
import numpy as np
from operator import attrgetter
import os
import random
import struct
import sys
import tempfile
from textwrap import dedent
import time
import timeit
import traceback

try:
    xrange
except NameError:  # Python 3
    xrange = range


class KiB(int):
    """ KibiBytes - multiples of the byte units for quantities of information. """
    def __new__(self, value=0):
        return 1024*value


BIG_TEST_FILE = 1  # MiBs or 0 for a small file.
SML_TEST_FILE = KiB(64)
EXECUTIONS = 100  # Number of times each "algorithm" is executed per timing run.
TIMINGS = 3  # Number of timing runs.
CHUNK_SIZE = KiB(8)
if BIG_TEST_FILE:
    FILE_SIZE = KiB(1024) * BIG_TEST_FILE
else:
    FILE_SIZE = SML_TEST_FILE  # For quicker testing.

# Common setup for all algorithms -- prefixed to each algorithm's setup.
COMMON_SETUP = dedent("""
    # Make accessible in algorithms.
    from __main__ import array, deque, get_buffer_size, mmap, np, struct
    from __main__ import ACCESS_READ, CHUNK_SIZE, FILE_SIZE, TEMP_FILENAME
    from functools import partial
    try:
        xrange
    except NameError:  # Python 3
        xrange = range
""")


def get_buffer_size(path):
    """ Determine optimal buffer size for reading files. """
    st = os.stat(path)
    try:
        bufsize = st.st_blksize # Available on some Unix systems (like Linux)
    except AttributeError:
        bufsize = io.DEFAULT_BUFFER_SIZE
    return bufsize

# Utility primarily for use when embedding additional algorithms into benchmark.
VERIFY_NUM_READ = """
    # Verify generator reads correct number of bytes (assumes values are correct).
    bytes_read = sum(1 for _ in file_byte_iterator(TEMP_FILENAME))
    assert bytes_read == FILE_SIZE, \
           'Wrong number of bytes generated: got {:,} instead of {:,}'.format(
                bytes_read, FILE_SIZE)
"""

TIMING = namedtuple('TIMING', 'label, exec_time')

class Algorithm(namedtuple('CodeFragments', 'setup, test')):

    # Default timeit "stmt" code fragment.
    _TEST = """
        #for b in file_byte_iterator(TEMP_FILENAME):  # Loop over every byte.
        #    pass  # Do stuff with byte...
        deque(file_byte_iterator(TEMP_FILENAME), maxlen=0)  # Data sink.
    """

    # Must overload __new__ because (named)tuples are immutable.
    def __new__(cls, setup, test=None):
        """ Dedent (unindent) code fragment string arguments.
        Args:
          `setup` -- Code fragment that defines things used by `test` code.
                     In this case it should define a generator function named
                     `file_byte_iterator()` that will be passed that name of a test file
                     of binary data. This code is not timed.
          `test` -- Code fragment that uses things defined in `setup` code.
                    Defaults to _TEST. This is the code that's timed.
        """
        test =  cls._TEST if test is None else test  # Use default unless one is provided.

        # Uncomment to replace all performance tests with one that verifies the correct
        # number of bytes values are being generated by the file_byte_iterator function.
        #test = VERIFY_NUM_READ

        return tuple.__new__(cls, (dedent(setup), dedent(test)))


algorithms = {

    'Aaron Hall (Py 2 version)': Algorithm("""
        def file_byte_iterator(path):
            with open(path, "rb") as file:
                callable = partial(file.read, 1024)
                sentinel = bytes() # or b''
                for chunk in iter(callable, sentinel):
                    for byte in chunk:
                        yield byte
    """),

    "codeape": Algorithm("""
        def file_byte_iterator(filename, chunksize=CHUNK_SIZE):
            with open(filename, "rb") as f:
                while True:
                    chunk = f.read(chunksize)
                    if chunk:
                        for b in chunk:
                            yield b
                    else:
                        break
    """),

    "codeape + iter + partial": Algorithm("""
        def file_byte_iterator(filename, chunksize=CHUNK_SIZE):
            with open(filename, "rb") as f:
                for chunk in iter(partial(f.read, chunksize), b''):
                    for b in chunk:
                        yield b
    """),

    "gerrit (struct)": Algorithm("""
        def file_byte_iterator(filename):
            with open(filename, "rb") as f:
                fmt = '{}B'.format(FILE_SIZE)  # Reads entire file at once.
                for b in struct.unpack(fmt, f.read()):
                    yield b
    """),

    'Rick M. (numpy)': Algorithm("""
        def file_byte_iterator(filename):
            for byte in np.fromfile(filename, 'u1'):
                yield byte
    """),

    "Skurmedel": Algorithm("""
        def file_byte_iterator(filename):
            with open(filename, "rb") as f:
                byte = f.read(1)
                while byte:
                    yield byte
                    byte = f.read(1)
    """),

    "Tcll (array.array)": Algorithm("""
        def file_byte_iterator(filename):
            with open(filename, "rb") as f:
                arr = array.array('B')
                arr.fromfile(f, FILE_SIZE)  # Reads entire file at once.
                for b in arr:
                    yield b
    """),

    "Vinay Sajip (read all into memory)": Algorithm("""
        def file_byte_iterator(filename):
            with open(filename, "rb") as f:
                bytes_read = f.read()  # Reads entire file at once.
            for b in bytes_read:
                yield b
    """),

    "Vinay Sajip (chunked)": Algorithm("""
        def file_byte_iterator(filename, chunksize=CHUNK_SIZE):
            with open(filename, "rb") as f:
                chunk = f.read(chunksize)
                while chunk:
                    for b in chunk:
                        yield b
                    chunk = f.read(chunksize)
    """),

}  # End algorithms

#
# Versions of algorithms that will only work in certain releases (or better) of Python.
#
if sys.version_info >= (3, 3):
    algorithms.update({

        'codeape + iter + partial + "yield from"': Algorithm("""
            def file_byte_iterator(filename, chunksize=CHUNK_SIZE):
                with open(filename, "rb") as f:
                    for chunk in iter(partial(f.read, chunksize), b''):
                        yield from chunk
        """),

        'codeape + "yield from"': Algorithm("""
            def file_byte_iterator(filename, chunksize=CHUNK_SIZE):
                with open(filename, "rb") as f:
                    while True:
                        chunk = f.read(chunksize)
                        if chunk:
                            yield from chunk
                        else:
                            break
        """),

        "jfs (mmap)": Algorithm("""
            def file_byte_iterator(filename):
                with open(filename, "rb") as f, \
                     mmap(f.fileno(), 0, access=ACCESS_READ) as s:
                    yield from s
        """),

        'Rick M. (numpy) + "yield from"': Algorithm("""
            def file_byte_iterator(filename):
            #    data = np.fromfile(filename, 'u1')
                yield from np.fromfile(filename, 'u1')
        """),

        'Vinay Sajip + "yield from"': Algorithm("""
            def file_byte_iterator(filename, chunksize=CHUNK_SIZE):
                with open(filename, "rb") as f:
                    chunk = f.read(chunksize)
                    while chunk:
                        yield from chunk  # Added in Py 3.3
                        chunk = f.read(chunksize)
        """),

    })  # End Python 3.3 update.

if sys.version_info >= (3, 5):
    algorithms.update({

        'Aaron Hall + "yield from"': Algorithm("""
            from pathlib import Path

            def file_byte_iterator(path):
                ''' Given a path, return an iterator over the file
                    that lazily loads the file.
                '''
                path = Path(path)
                bufsize = get_buffer_size(path)

                with path.open('rb') as file:
                    reader = partial(file.read1, bufsize)
                    for chunk in iter(reader, bytes()):
                        yield from chunk
        """),

    })  # End Python 3.5 update.

if sys.version_info >= (3, 8, 0):
    algorithms.update({

        'Vinay Sajip + "yield from" + "walrus operator"': Algorithm("""
            def file_byte_iterator(filename, chunksize=CHUNK_SIZE):
                with open(filename, "rb") as f:
                    while chunk := f.read(chunksize):
                        yield from chunk  # Added in Py 3.3
        """),

        'codeape + "yield from" + "walrus operator"': Algorithm("""
            def file_byte_iterator(filename, chunksize=CHUNK_SIZE):
                with open(filename, "rb") as f:
                    while chunk := f.read(chunksize):
                        yield from chunk
        """),

    })  # End Python 3.8.0 update.update.


#### Main ####

def main():
    global TEMP_FILENAME

    def cleanup():
        """ Clean up after testing is completed. """
        try:
            os.remove(TEMP_FILENAME)  # Delete the temporary file.
        except Exception:
            pass

    atexit.register(cleanup)

    # Create a named temporary binary file of pseudo-random bytes for testing.
    fd, TEMP_FILENAME = tempfile.mkstemp('.bin')
    with os.fdopen(fd, 'wb') as file:
         os.write(fd, bytearray(random.randrange(256) for _ in range(FILE_SIZE)))

    # Execute and time each algorithm, gather results.
    start_time = time.time()  # To determine how long testing itself takes.

    timings = []
    for label in algorithms:
        try:
            timing = TIMING(label,
                            min(timeit.repeat(algorithms[label].test,
                                              setup=COMMON_SETUP + algorithms[label].setup,
                                              repeat=TIMINGS, number=EXECUTIONS)))
        except Exception as exc:
            print('{} occurred timing the algorithm: "{}"\n  {}'.format(
                    type(exc).__name__, label, exc))
            traceback.print_exc(file=sys.stdout)  # Redirect to stdout.
            sys.exit(1)
        timings.append(timing)

    # Report results.
    print('Fastest to slowest execution speeds with {}-bit Python {}.{}.{}'.format(
            64 if sys.maxsize > 2**32 else 32, *sys.version_info[:3]))
    print('  numpy version {}'.format(np.version.full_version))
    print('  Test file size: {:,} KiB'.format(FILE_SIZE // KiB(1)))
    print('  {:,d} executions, best of {:d} repetitions'.format(EXECUTIONS, TIMINGS))
    print()

    longest = max(len(timing.label) for timing in timings)  # Len of longest identifier.
    ranked = sorted(timings, key=attrgetter('exec_time')) # Sort so fastest is first.
    fastest = ranked[0].exec_time
    for rank, timing in enumerate(ranked, 1):
        print('{:<2d} {:>{width}} : {:8.4f} secs, rel speed {:6.2f}x, {:6.2f}% slower '
              '({:6.2f} KiB/sec)'.format(
                    rank,
                    timing.label, timing.exec_time, round(timing.exec_time/fastest, 2),
                    round((timing.exec_time/fastest - 1) * 100, 2),
                    (FILE_SIZE/timing.exec_time) / KiB(1),  # per sec.
                    width=longest))
    print()
    mins, secs = divmod(time.time()-start_time, 60)
    print('Benchmark runtime (min:sec) - {:02d}:{:02d}'.format(int(mins),
                                                               int(round(secs))))

main()
Мартино
источник
Вы предполагаете, что я делаю yield from chunkвместо этого for byte in chunk: yield byte? Я думаю, что я должен ужесточить свой ответ с этим.
Аарон Холл
@ Аарон: Есть две версии вашего ответа в результатах Python 3, и одна из них использует yield from.
Мартино
хорошо, я обновил свой ответ. также я предлагаю вам отказаться, enumerateпоскольку итерацию следует понимать как завершенную - если нет, то, что я проверял последний раз - у enumerate есть некоторые накладные расходы с затратами по сравнению с ведением бухгалтерского учета для индекса с + = 1, так что вы могли бы альтернативно вести учет в своем собственный код Или даже перейти в deque с maxlen=0.
Аарон Холл
@ Аарон: согласен по поводу enumerate. Спасибо за ответ. Буду добавлять обновление в мой пост, в котором его нет (хотя я не думаю, что это сильно изменит результаты). Также будет добавлен ответ на numpyоснове @Rick M.
Мартино
Немного больше обзора кода: я не думаю, что сейчас имеет смысл писать ответы на Python 2 - я бы рассмотрел удаление Python 2, так как я ожидаю, что вы будете использовать 64-битный Python 3.7 или 3.8. Вы можете настроить очистку, чтобы завершить ее с помощью atexit и частичного применения. Опечатка: "проверить". Я не вижу смысла в дублировании тестовых строк - они вообще разные? Я полагаю, что если вы используете super().вместо tuple.своего, __new__вы можете использовать namedtupleимена атрибутов вместо индексов.
Аарон Холл
3

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

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.

Tcll
источник
1
'массив' импортируется 'из массива импорта массива'
quanly_mc
@quanly_mc да, спасибо, что поймали это, и извините, я забыл включить это, редактируя сейчас.
18:00