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


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()

Пітон 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 це не потрібно.

Пітон 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)

пітон 3.8

Відтепер завдяки: = оператору вищевказаний код можна записати коротше.

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

40
Читання файлів у байтах - це кошмар продуктивності. Це не може бути найкращим рішенням, доступним у python. Цей код слід використовувати обережно.
usr

7
@usr: Добре, що файлові об’єкти буферуються всередині, і навіть тому це саме те, про що вимагали. Не кожен сценарій потребує оптимальної продуктивності.
Skurmedel

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 .


3
@codeape Просто те, що я шукаю. Але, як ви визначаєте шматки? Чи може це бути довільне значення?
swdev

3
@swdev: У прикладі використовується фрагмент розміру 8192 байт . Параметр для file.read () - функції просто вказує розмір, тобто кількість байтів, які слід прочитати. кодеп вибрав 8192 Byte = 8 kB(насправді це, KiBале це не так відомо). Значення є "абсолютно" випадковим, але 8 кБ, здається, є відповідним значенням: не надто багато пам’яті витрачається, і все ще немає "занадто багато" операцій з читання, як у прийнятій відповіді Скурмеделя ...
mozzbozz

3
Файлова система вже буферизує фрагменти даних, тому цей код є зайвим. Краще читати байт за раз.
стартував

17
Хоча це вже швидше, ніж прийнята відповідь, це може бути збільшено ще на 20-25%, замінивши весь внутрішній for b in chunk:цикл на yield from chunk. Цю форму yieldдодано в Python 3.3 (див. Вирази виходу ).
мартино

3
Гм здається малоймовірним, посилання?
кодап

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контуру:

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на порядок швидше.


Останній приклад мені здався дуже цікавим. Шкода, що немає еквівалентних numpyмасивів на базі пам'яті (байтів).
мартіно

1
@martineau є, numpy.memmap()і ви можете отримувати дані по одному байту (ctypes.data). Ви можете вважати нумеровані масиви лише трохи більше, ніж краплі в пам'яті + метадані.
jfs

jfs: Дякую, чудові новини! Не знав такого, що існує. Чудова відповідь, BTW.
martineau

25

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

Новим у Python 3.5 є pathlibмодуль, який має зручний метод, зокрема, читати у файлі у вигляді байтів, що дозволяє нам перебирати байти. Я вважаю це гідною (якщо швидкою та брудною) відповіддю:

import pathlib

for byte in pathlib.Path(path).read_bytes():
    print(byte)

Цікаво, що це єдина відповідь на згадку pathlib.

У Python 2 ви, ймовірно, зробите це (як пропонує також Віней Саджип):

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, латинська тощо), що повинні бути відкриті без 'b'прапора.


2
Це ТАК набагато краще ... дякую за це. Я знаю, що не завжди весело повертатися до відповіді на два роки, але я ціную, що ти це зробив. Мені особливо подобається підзаголовок "Не повторюй за рядками" :-)
Флоріс,

1
Привіт Аароне, чи є якась причина, чому ви вирішили використовувати path = Path(path), with path.open('rb') as file:замість вбудованої відкритої функції замість цього? Вони обидва роблять те саме, що правильно?
Джошуа Йонатан

1
@JoshuaYonathan Я використовую 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 внутрішньо - не потрібно читати шматки
  • Принцип DRY - не повторюйте прочитаний рядок
  • з заявою забезпечує чистий файл закрити
  • 'байт' оцінюється як 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 Мб на комп'ютері з операційною пам’яттю Window 10, 8 Gb та 32-розрядною програмою Python 3.5. Мені рекомендував колега використовувати numpyзамість цього, і це творить чудеса.

На сьогодні найшвидший для читання весь двійковий файл (який я перевірив):

import numpy as np

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

Довідково

Множина швидше, ніж будь-які інші методи досі. Сподіваюся, це комусь допоможе!


3
Приємно, але його не можна використовувати у двійковому файлі, що містить різні типи даних.
Nirmal

@Nirmal: Питання стосується циклічного перегляду над байтом охоплення, тому незрозуміло, чи має ваш коментар про різні типи даних якесь значення.
мартіно

1
Рік: Ваш код робить зовсім не те саме, що й інші, а саме - перебирання через кожен байт. Якщо до цього додано, це не швидше, ніж більшість інших, принаймні за результатами мого еталону . Насправді, схоже, це один із повільних підходів. Якщо обробка, виконана для кожного байта (що б там не було), могла бути зроблена через numpy, то, можливо, варто.
мартіно

@martineau Дякую за ваші коментарі, так, я розумію, що питання полягає у перегляді кожного байту, а не просто завантаженні всього за один раз, але є інші відповіді на це запитання, які також вказують на прочитання всього вмісту, а отже, і моя відповідь
Рік М.

4

Якщо у вас є чимало двійкових даних для читання, можливо, ви захочете розглянути модуль Stru . Це задокументовано як перетворення "між типами 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 MiB (на що запустив майже годину) і отримав результати, які були порівнянні з показаними вище.

Ось код, який використовується для тестування:

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? Я думаю, що мені слід посилити свою відповідь на це.
Аарон Холл

@Aaron: У результатах Python 3 є дві версії, і одна з них використовує yield from.
мартіно

добре, я оновив свою відповідь. також я пропоную вам відмовитися, enumerateоскільки ітерація повинна розумітися як завершена - якщо ні, останнє я перевірив - перерахування має трохи накладних витрат із витратами на ведення бухгалтерського обліку для індексу з + = 1, так що ви, можливо, будете робити бухгалтерію у вашому власний код. Або навіть перейти до деке з maxlen=0.
Аарон Холл

@Aaron: Погодьтеся щодо 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)

якщо ви хочете повторити символи замість ints, ви можете просто використовувати data = file.read(), що має бути об'єктом bytes () у py3.


1
"масив" імпортується "з масиву імпорту масиву"
quanly_mc

@quanly_mc так, дякую, що це зрозумів, і вибачте, що забув включити це, редагуючи зараз.
Tcll
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.