Dengan Python, bagaimana cara membaca dalam file biner dan mengulangi setiap byte file itu?
Dengan Python, bagaimana cara membaca dalam file biner dan mengulangi setiap byte file itu?
Jawaban:
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.
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 .
8192 Byte = 8 kB
(sebenarnya itu KiB
tapi 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 ...
for b in chunk:
loop paling dalam dengan yield from chunk
. Bentuk yield
ini ditambahkan dalam Python 3.3 (lihat Yield Expressions ).
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 with
pernyataan tersedia dalam Python 2,5 dan lebih besar.
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.py
utilitas 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 == 32768
di 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.
mmap
memungkinkan Anda untuk memperlakukan file sebagai bytearray
objek 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
mmap
mendukung notasi slice. Misalnya, mm[i:i+len]
mengembalikan len
byte 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 mmap
memori lebih banyak daripada file.read(1)
, tetapi mmap
urutan besarnya lebih cepat.
numpy
array (byte) memori-dipetakan setara .
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.
Membaca file biner dengan Python dan mengulang setiap byte
Baru dalam Python 3.5 adalah pathlib
modul, 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 iter
fungsi dengan callable, sentinel
tanda 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.)
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.read
blok sampai mendapat semua byte yang diminta atau EOF
. file.read1
memungkinkan kita untuk menghindari pemblokiran, dan itu dapat kembali lebih cepat karena ini. Tidak ada jawaban lain yang menyebutkan ini juga.
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 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.
path = Path(path), with path.open('rb') as file:
daripada menggunakan fungsi terbuka built-in saja? Mereka berdua melakukan hal yang sama benar?
Path
objek 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 open
builtin, tetapi ada banyak sisi baiknya ketika menulis program agar programmer menggunakan Path
objek.
file_byte_iterator
jauh lebih cepat daripada semua metode yang saya coba di halaman ini. Kudos untuk Anda!
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:
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)
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 numpy
dan 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')
Banyak orang lebih cepat daripada metode lain sejauh ini. Semoga ini bisa membantu seseorang!
numpy
, maka mungkin bermanfaat.
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 struct
dokumentasi):
>>> 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.
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()
yield from chunk
bukan for byte in chunk: yield byte
? Saya pikir saya harus memperketat jawaban saya dengan itu.
yield from
.
enumerate
karena 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
.
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 numpy
jawaban berbasis @Rick M.
super().
alih-alih tuple.
Anda, __new__
Anda bisa menggunakan namedtuple
nama atribut alih-alih indeks.
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.