Membaca file biner dan mengulang setiap byte

377

Dengan Python, bagaimana cara membaca dalam file biner dan mengulangi setiap byte file itu?

Jesse Vogt
sumber

Jawaban:

387

Python 2.4 dan Sebelumnya

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)

Perhatikan bahwa pernyataan with tidak tersedia dalam versi Python di bawah 2.5. Untuk menggunakannya di v 2.5 Anda harus mengimpornya:

from __future__ import with_statement

Dalam 2.6 ini tidak diperlukan.

Python 3

Dalam Python 3, ini sedikit berbeda. Kita tidak akan lagi mendapatkan karakter mentah dari stream dalam mode byte tetapi objek byte, jadi kita perlu mengubah kondisinya:

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

Atau seperti yang dikatakan benhoyt, lewatkan yang tidak sama dan manfaatkan fakta yang b""dinilai salah. Ini membuat kode tersebut kompatibel antara 2.6 dan 3.x tanpa perubahan apa pun. Ini juga akan menyelamatkan Anda dari mengubah kondisi jika Anda beralih dari mode byte ke teks atau sebaliknya.

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

python 3.8

Mulai sekarang terima kasih kepada: = operator kode di atas dapat ditulis dengan cara yang lebih singkat.

with open("myfile", "rb") as f:
    while (byte := f.read(1)):
        # Do stuff with byte.
Skurmedel
sumber
40
Membaca file byte-wise adalah mimpi buruk kinerja. Ini tidak bisa menjadi solusi terbaik yang tersedia dalam python. Kode ini harus digunakan dengan hati-hati.
usr
7
@ Usr: Yah objek file buffer secara internal, dan meskipun demikian inilah yang diminta. Tidak semua skrip membutuhkan kinerja optimal.
Skurmedel
4
@mezhaka: Jadi Anda mengubahnya dari read (1) menjadi read (bufsize) dan pada while-loop Anda melakukan for-in ... contohnya masih berdiri.
Skurmedel
3
@ Usr: perbedaan kinerja dapat sebanyak 200 kali untuk kode yang saya coba .
jfs
2
@ usr - tergantung pada berapa banyak byte yang ingin Anda proses. Jika jumlahnya sedikit, kode "jelek" berkinerja tetapi mudah dimengerti bisa lebih disukai. Pemborosan siklus CPU dikompensasi untuk menyimpan "siklus CPU pembaca" ketika mempertahankan kode.
IllvilJa
172

Generator ini menghasilkan byte dari file, membaca file dalam potongan:

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)

Lihat dokumentasi Python untuk informasi tentang iterator dan generator .

kode kode
sumber
3
@codeape Hanya apa yang saya cari. Tapi, bagaimana Anda menentukan chunksize? Bisakah itu nilai yang sewenang-wenang?
swdev
3
@swdev: Contoh ini menggunakan chunksize 8192 Bytes . Parameter untuk fungsi file.read () - cukup menentukan ukurannya, yaitu jumlah Bytes yang akan dibaca. codeape memilih 8192 Byte = 8 kB(sebenarnya itu KiBtapi itu tidak dikenal secara umum). Nilainya "benar-benar" acak tetapi 8 kB tampaknya merupakan nilai yang sesuai: tidak terlalu banyak memori yang terbuang dan masih tidak ada "terlalu banyak" operasi baca seperti dalam jawaban yang diterima oleh Skurmedel ...
mozzbozz
3
Sistem file sudah buffer potongan data, jadi kode ini berlebihan. Lebih baik membaca satu byte pada satu waktu.
stark
17
Meskipun sudah lebih cepat dari jawaban yang diterima, ini bisa dipercepat oleh 20-25% lainnya dengan mengganti seluruh for b in chunk:loop paling dalam dengan yield from chunk. Bentuk yieldini ditambahkan dalam Python 3.3 (lihat Yield Expressions ).
martineau
3
Hmm sepertinya tidak mungkin, tautan?
codeape
54

Jika file tidak terlalu besar yang menahannya dalam memori adalah masalah:

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

di mana process_byte mewakili beberapa operasi yang ingin Anda lakukan pada byte yang lewat.

Jika Anda ingin memproses chunk sekaligus:

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)

The withpernyataan tersedia dalam Python 2,5 dan lebih besar.

Vinay Sajip
sumber
1
Anda mungkin tertarik dengan tolok ukur yang baru saja saya posting.
martineau
37

Untuk membaca file - satu byte pada satu waktu (mengabaikan buffering) - Anda bisa menggunakan fungsi built-in dua argumeniter(callable, sentinel) :

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

Itu panggilan file.read(1)sampai tidak menghasilkan apa-apa b''(bytestring kosong). Memori tidak tumbuh tanpa batas untuk file besar. Anda dapat beralih buffering=0 ke open(), untuk menonaktifkan buffering - itu menjamin bahwa hanya satu byte dibaca per iterasi (lambat).

with-Statement menutup file secara otomatis - termasuk case ketika kode di bawahnya memunculkan exception.

Meskipun ada penyangga internal secara default, masih tidak efisien untuk memproses satu byte pada suatu waktu. Misalnya, inilah blackhole.pyutilitas yang memakan semua yang diberikan:

#!/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)

Contoh:

$ dd if=/dev/zero bs=1M count=1000 | python3 blackhole.py

Ini memproses ~ 1,5 GB / s saat chunksize == 32768di komputer saya dan hanya ~ 7,5 MB / s saat chunksize == 1. Artinya, 200 kali lebih lambat untuk membaca satu byte pada suatu waktu. Pertimbangkan jika Anda dapat menulis ulang pemrosesan Anda untuk menggunakan lebih dari satu byte pada suatu waktu dan jika Anda membutuhkan kinerja.

mmapmemungkinkan Anda untuk memperlakukan file sebagai bytearrayobjek file dan secara bersamaan. Ini dapat berfungsi sebagai alternatif untuk memuat seluruh file dalam memori jika Anda perlu mengakses kedua antarmuka. Secara khusus, Anda dapat mengulangi satu byte pada suatu waktu melalui file yang dipetakan dengan memori hanya menggunakan for-loop polos :

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

mmapmendukung notasi slice. Misalnya, mm[i:i+len]mengembalikan lenbyte dari file mulai dari posisi i. Protokol manajer konteks tidak didukung sebelum Python 3.2; Anda perlu menelepon mm.close()secara eksplisit dalam hal ini. Iterasi setiap byte menggunakan mmapmemori lebih banyak daripada file.read(1), tetapi mmapurutan besarnya lebih cepat.

jfs
sumber
Saya menemukan contoh terakhir sangat menarik. Sayang sekali tidak ada numpyarray (byte) memori-dipetakan setara .
martineau
1
@martineau ada numpy.memmap()dan Anda bisa mendapatkan data satu byte setiap kali (ctypes.data). Anda bisa menganggap array numpy hanya sedikit lebih dari gumpalan di memori + metadata.
jfs
jfs: Terima kasih, berita bagus! Tidak tahu benda seperti itu ada. Jawaban yang bagus, BTW.
martineau
25

Membaca file biner dengan Python dan mengulang setiap byte

Baru dalam Python 3.5 adalah pathlibmodul, yang memiliki metode kenyamanan khusus untuk membaca dalam file sebagai byte, memungkinkan kita untuk beralih pada byte. Saya menganggap ini jawaban yang layak (jika cepat dan kotor):

import pathlib

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

Menarik bahwa ini adalah satu-satunya jawaban untuk disebutkan pathlib.

Dalam Python 2, Anda mungkin akan melakukan ini (seperti yang disarankan Vinay Sajip):

with open(path, 'b') as file:
    for byte in file.read():
        print(byte)

Jika file mungkin terlalu besar untuk di-in-memory, Anda akan memotongnya, secara idiomatis, menggunakan iterfungsi dengan callable, sentineltanda tangan - versi 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)

(Beberapa jawaban lain menyebutkan ini, tetapi sedikit yang menawarkan ukuran baca yang masuk akal.)

Praktik terbaik untuk file besar atau buffered / reading interaktif

Mari kita membuat fungsi untuk melakukan ini, termasuk penggunaan idiomatik dari perpustakaan standar untuk 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

Perhatikan bahwa kami menggunakan file.read1. file.readblok sampai mendapat semua byte yang diminta atau EOF. file.read1memungkinkan kita untuk menghindari pemblokiran, dan itu dapat kembali lebih cepat karena ini. Tidak ada jawaban lain yang menyebutkan ini juga.

Demonstrasi penggunaan praktik terbaik:

Mari kita membuat file dengan megabyte (sebenarnya mebibyte) dari data pseudorandom:

import random
import pathlib
path = 'pseudorandom_bytes'
pathobj = pathlib.Path(path)

pathobj.write_bytes(
  bytes(random.randint(0, 255) for _ in range(2**20)))

Sekarang mari kita beralih dan mewujudkannya dalam memori:

>>> l = list(file_byte_iterator(path))
>>> len(l)
1048576

Kami dapat memeriksa bagian mana pun dari data, misalnya, 100 byte terakhir dan 100 byte pertama:

>>> 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]

Jangan beralih berdasarkan baris untuk file biner

Jangan lakukan hal berikut - ini menarik sepotong ukuran acak hingga mencapai karakter baris baru - terlalu lambat ketika potongan terlalu kecil, dan mungkin terlalu besar juga:

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

Di atas hanya baik untuk file teks yang dapat dibaca manusia secara semantik (seperti teks biasa, kode, markup, penurunan harga, dll ... pada dasarnya apa saja yang ascii, utf, latin, dll ... disandikan) yang harus Anda buka tanpa 'b'bendera.

Aaron Hall
sumber
2
Ini SANGAT jauh lebih baik ... terima kasih telah melakukan ini. Saya tahu itu tidak selalu menyenangkan untuk kembali ke jawaban dua tahun, tapi saya menghargai Anda melakukannya. Saya terutama menyukai subheading "Don't iterate by lines" :-)
Floris
1
Hai Aaron, apakah ada alasan mengapa Anda memilih untuk menggunakan path = Path(path), with path.open('rb') as file:daripada menggunakan fungsi terbuka built-in saja? Mereka berdua melakukan hal yang sama benar?
Joshua Yonathan
1
@ JoshuaYonathan Saya menggunakan Pathobjek karena ini adalah cara baru yang sangat nyaman untuk menangani jalur. Alih-alih meneruskan sebuah string ke fungsi "benar" yang dipilih dengan cermat, kita bisa memanggil metode pada objek path, yang pada dasarnya berisi sebagian besar fungsi penting yang Anda inginkan dengan apa yang secara semantik adalah string path. Dengan IDE yang dapat menginspeksi, kita juga dapat dengan mudah mendapatkan pelengkapan otomatis. Kita bisa melakukan hal yang sama dengan openbuiltin, tetapi ada banyak sisi baiknya ketika menulis program agar programmer menggunakan Pathobjek.
Aaron Hall
1
Metode terakhir yang Anda sebutkan menggunakan fungsi ini, file_byte_iteratorjauh lebih cepat daripada semua metode yang saya coba di halaman ini. Kudos untuk Anda!
Rick M.
@RickM: Anda mungkin tertarik dengan tolok ukur yang baru saya posting.
martineau
19

Untuk meringkas semua poin cemerlang chrispy, Skurmedel, Ben Hoyt dan Peter Hansen, ini akan menjadi solusi optimal untuk memproses file biner satu byte pada satu waktu:

with open("myfile", "rb") as f:
    while True:
        byte = f.read(1)
        if not byte:
            break
        do_stuff_with(ord(byte))

Untuk versi python 2.6 dan di atasnya, karena:

  • buffer python secara internal - tidak perlu membaca bongkahan
  • Prinsip KERING - jangan ulangi baris baca
  • dengan pernyataan memastikan file bersih ditutup
  • 'byte' dievaluasi menjadi false ketika tidak ada lagi byte (bukan ketika byte nol)

Atau gunakan solusi JF Sebastians untuk meningkatkan kecepatan

from functools import partial

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

Atau jika Anda menginginkannya sebagai fungsi generator seperti yang ditunjukkan oleh 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)
Holger Bille
sumber
2
Seperti jawaban yang ditautkan mengatakan, membaca / memproses satu byte pada suatu waktu masih lambat di Python bahkan jika membaca buffered. Kinerja dapat ditingkatkan secara drastis jika beberapa byte pada satu waktu dapat diproses seperti pada contoh dalam jawaban terkait: 1.5GB / s vs 7.5MB / s.
jfs
6

Python 3, baca semua file sekaligus:

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

Anda dapat mengulangi apa pun yang Anda inginkan menggunakan datavariabel.

Mircea
sumber
6

Setelah mencoba semua hal di atas dan menggunakan jawaban dari @Aaron Hall, saya mendapatkan kesalahan memori untuk file ~ 90 Mb pada komputer yang menjalankan Window 10, 8 Gb RAM dan Python 3.5 32-bit. Saya direkomendasikan oleh seorang kolega untuk menggunakan numpydan itu bekerja dengan baik.

Sejauh ini, yang tercepat untuk membaca seluruh file biner (yang telah saya uji) adalah:

import numpy as np

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

Referensi

Banyak orang lebih cepat daripada metode lain sejauh ini. Semoga ini bisa membantu seseorang!

Rick M.
sumber
3
Bagus, tetapi tidak dapat digunakan pada file biner yang berisi tipe data berbeda.
Nirmal
@Nirmal: Pertanyaannya adalah tentang pengulangan byte jangkauan, jadi tidak jelas apakah komentar Anda tentang tipe data yang berbeda memiliki kaitan.
martineau
1
Rick: Kode Anda tidak melakukan hal yang sama seperti yang lain - yaitu mengulang setiap byte. Jika itu ditambahkan ke dalamnya, itu tidak lebih cepat daripada mayoritas yang lain menurut setidaknya menurut hasil dalam tolok ukur saya . Bahkan tampaknya itu adalah salah satu pendekatan yang lebih lambat. Jika pemrosesan dilakukan untuk setiap byte (apa pun itu) adalah sesuatu yang bisa dilakukan melalui numpy, maka mungkin bermanfaat.
martineau
@martineau Terima kasih atas komentar Anda, ya saya mengerti bahwa pertanyaannya adalah tentang mengulang setiap byte dan tidak hanya memuat semuanya dalam sekali jalan, tetapi ada jawaban lain dalam pertanyaan ini yang juga menunjuk untuk membaca semua konten dan karenanya jawaban saya
Rick M.
4

Jika Anda memiliki banyak data biner untuk dibaca, Anda mungkin ingin mempertimbangkan modul struct . Ini didokumentasikan sebagai konversi "antara tipe C dan Python", tetapi tentu saja, byte adalah byte, dan apakah itu dibuat sebagai tipe C tidak masalah. Misalnya, jika data biner Anda berisi dua bilangan bulat 2-byte dan satu bilangan bulat 4-byte, Anda dapat membacanya sebagai berikut (contoh diambil dari structdokumentasi):

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

Anda mungkin menemukan ini lebih nyaman, lebih cepat, atau keduanya, daripada secara eksplisit mengulang konten file.

gerrit
sumber
4

Posting ini sendiri bukan jawaban langsung untuk pertanyaan itu. Sebaliknya, itu adalah patokan yang dapat diperluas yang digerakkan oleh data yang dapat digunakan untuk membandingkan banyak jawaban (dan variasi penggunaan fitur baru yang ditambahkan di versi Python yang lebih modern dan lebih baru) yang telah diposting ke pertanyaan ini - dan karenanya harus membantu dalam menentukan mana yang memiliki kinerja terbaik.

Dalam beberapa kasus, saya telah memodifikasi kode dalam jawaban yang direferensikan untuk membuatnya kompatibel dengan kerangka acuan.

Pertama, berikut adalah hasil untuk versi Python 2 & 3 terbaru:

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

Saya juga menjalankannya dengan file tes 10 MiB yang jauh lebih besar (yang memakan waktu hampir satu jam untuk menjalankan) dan mendapatkan hasil kinerja yang sebanding dengan yang ditunjukkan di atas.

Berikut kode yang digunakan untuk melakukan pembandingan:

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()
martineau
sumber
Apakah Anda asumsi saya lakukan yield from chunkbukan for byte in chunk: yield byte? Saya pikir saya harus memperketat jawaban saya dengan itu.
Aaron Hall
@ Harun: Ada dua versi jawaban Anda dalam hasil Python 3 dan salah satunya menggunakan yield from.
martineau
ok, saya sudah memperbarui jawaban saya. juga saya sarankan Anda turun enumeratekarena iterasi harus dipahami untuk menyelesaikan - jika tidak, terakhir saya periksa - enumerate memiliki sedikit overhead dengan biaya lebih dari melakukan pembukuan untuk indeks dengan + = 1, sehingga Anda dapat melakukan alternatif pembukuan di Anda kode sendiri. Atau bahkan lulus ke deque dengan maxlen=0.
Aaron Hall
@ Harun: Setuju tentang enumerate. Terima kasih untuk umpan baliknya. Akan menambahkan pembaruan ke posting saya yang tidak memilikinya (walaupun saya pikir itu tidak banyak mengubah hasilnya). Juga akan menambahkan numpyjawaban berbasis @Rick M.
martineau
Ulasan kode yang lebih sedikit: Saya rasa tidak masuk akal untuk menulis jawaban untuk Python 2 pada titik ini - saya akan mempertimbangkan menghapus Python 2 karena saya berharap Anda menggunakan 64 bit Python 3.7 atau 3.8. Anda dapat mengatur pembersihan untuk pergi di akhir dengan atexit dan aplikasi parsial. Ketikan: "verifikasi". Saya tidak merasakan adanya duplikasi string tes - apakah semuanya berbeda? Saya membayangkan jika Anda menggunakan super().alih-alih tuple.Anda, __new__Anda bisa menggunakan namedtuplenama atribut alih-alih indeks.
Aaron Hall
3

jika Anda mencari sesuatu yang cepat, berikut ini adalah metode yang saya gunakan selama bertahun-tahun:

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)

jika Anda ingin mengulangi karakter bukan int, Anda bisa menggunakan data = file.read(), yang seharusnya menjadi objek byte () di py3.

Tcll
sumber
1
'array' diimpor oleh 'from array import array'
quanly_mc
@quanly_mc ya, terima kasih sudah menangkapnya, dan maaf saya lupa memasukkannya, edit sekarang.
Tcll