Wie lese ich in Python eine Binärdatei ein und durchlaufe jedes Byte dieser Datei?
Wie lese ich in Python eine Binärdatei ein und durchlaufe jedes Byte dieser Datei?
Antworten:
Python 2.4 und früher
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)
Beachten Sie, dass die with-Anweisung in Python-Versionen unter 2.5 nicht verfügbar ist. Um es in Version 2.5 zu verwenden, müssen Sie es importieren:
from __future__ import with_statement
In 2.6 wird dies nicht benötigt.
Python 3
In Python 3 ist das etwas anders. Wir erhalten im Byte-Modus keine Rohzeichen mehr aus dem Stream, sondern Byte-Objekte. Daher müssen wir die Bedingung ändern:
with open("myfile", "rb") as f:
byte = f.read(1)
while byte != b"":
# Do stuff with byte.
byte = f.read(1)
Oder überspringen Sie, wie benhoyt sagt, das Ungleiche und nutzen Sie die Tatsache, dass es sich b""
um falsch handelt. Dadurch ist der Code ohne Änderungen zwischen 2.6 und 3.x kompatibel. Es würde Sie auch davor bewahren, die Bedingung zu ändern, wenn Sie vom Bytemodus zum Text oder umgekehrt wechseln.
with open("myfile", "rb") as f:
byte = f.read(1)
while byte:
# Do stuff with byte.
byte = f.read(1)
Python 3.8
Dank: = operator kann der obige Code von nun an kürzer geschrieben werden.
with open("myfile", "rb") as f:
while (byte := f.read(1)):
# Do stuff with byte.
Dieser Generator liefert Bytes aus einer Datei und liest die Datei in Blöcken:
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)
Informationen zu Iteratoren und Generatoren finden Sie in der Python-Dokumentation .
8192 Byte = 8 kB
(eigentlich ist es KiB
aber das ist nicht so allgemein bekannt). Der Wert ist "völlig" zufällig, aber 8 kB scheinen ein angemessener Wert zu sein: Es wird nicht zu viel Speicher verschwendet und es gibt immer noch nicht "zu viele" Lesevorgänge wie in der akzeptierten Antwort von Skurmedel ...
for b in chunk:
Schleife durch ersetzt wird yield from chunk
. Diese Form von yield
wurde in Python 3.3 hinzugefügt (siehe Ertragsausdrücke ).
Wenn die Datei nicht zu groß ist, ist es ein Problem, sie im Speicher zu halten:
with open("filename", "rb") as f:
bytes_read = f.read()
for b in bytes_read:
process_byte(b)
Dabei steht process_byte für eine Operation, die Sie für das übergebene Byte ausführen möchten.
Wenn Sie jeweils einen Block verarbeiten möchten:
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)
Die with
Anweisung ist in Python 2.5 und höher verfügbar.
Um eine Datei zu lesen - jeweils ein Byte (ohne Berücksichtigung der Pufferung) - können Sie die integrierte Funktion mit zwei Argumenten iter(callable, sentinel)
verwenden :
with open(filename, 'rb') as file:
for byte in iter(lambda: file.read(1), b''):
# Do stuff with byte
Es ruft auf, file.read(1)
bis es nichts zurückgibt b''
(leerer Bytestring). Der Speicher wächst nicht unbegrenzt für große Dateien. Sie können buffering=0
an übergeben open()
, um die Pufferung zu deaktivieren - dies garantiert, dass nur ein Byte pro Iteration gelesen wird (langsam).
with
-statement schließt die Datei automatisch - einschließlich des Falls, in dem der darunter liegende Code eine Ausnahme auslöst.
Trotz des Vorhandenseins einer internen Pufferung ist es immer noch ineffizient, jeweils ein Byte zu verarbeiten. Hier ist zum Beispiel das blackhole.py
Dienstprogramm, das alles isst, was es gibt:
#!/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)
Beispiel:
$ dd if=/dev/zero bs=1M count=1000 | python3 blackhole.py
Es verarbeitet ~ 1,5 GB / s , wenn chunksize == 32768
auf meiner Maschine und nur ~ 7,5 MB / s bei chunksize == 1
. Das heißt, es ist 200-mal langsamer, jeweils ein Byte zu lesen. Berücksichtigen Sie, ob Sie Ihre Verarbeitung so umschreiben können, dass mehr als ein Byte gleichzeitig verwendet wird, und ob Sie Leistung benötigen.
mmap
Mit dieser Option können Sie eine Datei gleichzeitig als bytearray
und als Dateiobjekt behandeln. Es kann als Alternative zum Laden der gesamten Datei in den Speicher dienen, wenn Sie Zugriff auf beide Schnittstellen benötigen. Insbesondere können Sie jeweils ein Byte über eine Speicherzuordnungsdatei iterieren, indem Sie einfach eine einfache for
Schleife verwenden:
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
unterstützt die Slice-Notation. Beispielsweise mm[i:i+len]
kehrt len
Bytes aus der Datei , beginnend an Position i
. Das Kontextmanagerprotokoll wird vor Python 3.2 nicht unterstützt. Sie müssen mm.close()
in diesem Fall explizit aufrufen . Das Durchlaufen jedes Bytes mit mmap
verbraucht mehr Speicher als file.read(1)
, ist jedoch mmap
um eine Größenordnung schneller.
numpy
speicherabgebildeten (Byte-) Arrays gibt.
numpy.memmap()
und Sie können die Daten byteweise abrufen (ctypes.data). Sie können sich numpy Arrays als etwas mehr als Blobs in Speicher + Metadaten vorstellen.
Binärdatei in Python lesen und jedes Byte durchlaufen
Neu in Python 3.5 ist das pathlib
Modul, das eine praktische Methode zum Einlesen einer Datei als Bytes bietet, mit der wir die Bytes durchlaufen können. Ich halte dies für eine anständige (wenn auch schnelle und schmutzige) Antwort:
import pathlib
for byte in pathlib.Path(path).read_bytes():
print(byte)
Interessant, dass dies die einzige zu erwähnende Antwort ist pathlib
.
In Python 2 würden Sie dies wahrscheinlich tun (wie auch Vinay Sajip vorschlägt):
with open(path, 'b') as file:
for byte in file.read():
print(byte)
Für den Fall, dass die Datei zu groß ist, um über den Arbeitsspeicher zu iterieren, würden Sie sie idiomatisch unter Verwendung der iter
Funktion mit der callable, sentinel
Signatur - der Python 2-Version - aufteilen:
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)
(Einige andere Antworten erwähnen dies, aber nur wenige bieten eine vernünftige Lesegröße.)
Erstellen wir dazu eine Funktion, einschließlich der idiomatischen Verwendung der Standardbibliothek für 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
Beachten Sie, dass wir verwenden file.read1
. file.read
blockiert, bis alle angeforderten Bytes oder EOF
.file.read1
ermöglicht es uns, das Blockieren zu vermeiden, und es kann dadurch schneller zurückkehren. Keine anderen Antworten erwähnen dies ebenfalls.
Lassen Sie uns eine Datei mit einem Megabyte (eigentlich Mebibyte) pseudozufälliger Daten erstellen:
import random
import pathlib
path = 'pseudorandom_bytes'
pathobj = pathlib.Path(path)
pathobj.write_bytes(
bytes(random.randint(0, 255) for _ in range(2**20)))
Lassen Sie uns nun darüber iterieren und es im Gedächtnis materialisieren:
>>> l = list(file_byte_iterator(path))
>>> len(l)
1048576
Wir können jeden Teil der Daten untersuchen, zum Beispiel die letzten 100 und die ersten 100 Bytes:
>>> 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]
Gehen Sie nicht wie folgt vor - dies zieht einen Block beliebiger Größe, bis ein Zeilenumbruchzeichen erreicht wird - zu langsam, wenn die Blöcke zu klein und möglicherweise auch zu groß sind:
with open(path, 'rb') as file:
for chunk in file: # text newline iteration - not for bytes
yield from chunk
Das Obige ist nur für semantisch lesbare Textdateien (wie Klartext, Code, Markup, Markdown usw.) geeignet, die Sie ohne das 'b'
Flag öffnen sollten .
path = Path(path), with path.open('rb') as file:
stattdessen die eingebaute offene Funktion zu verwenden? Sie machen beide dasselbe richtig?
Path
Objekt, weil es eine sehr bequeme neue Art ist, mit Pfaden umzugehen . Anstatt eine Zeichenfolge in die sorgfältig ausgewählten "richtigen" Funktionen zu übergeben, können wir einfach die Methoden für das Pfadobjekt aufrufen, das im Wesentlichen die meisten wichtigen Funktionen enthält, die Sie mit einer semantisch angegebenen Pfadzeichenfolge benötigen. Mit IDEs, die inspizieren können, können wir auch die automatische Vervollständigung einfacher erreichen. Wir könnten dasselbe mit dem open
eingebauten Programm erreichen, aber es gibt viele Vorteile beim Schreiben des Programms, damit der Programmierer das Path
Objekt stattdessen verwenden kann.
file_byte_iterator
ist viel schneller als alle Methoden, die ich auf dieser Seite ausprobiert habe. Hut ab!
Um alle brillanten Punkte von chrispy, Skurmedel, Ben Hoyt und Peter Hansen zusammenzufassen, wäre dies die optimale Lösung, um eine Binärdatei byteweise zu verarbeiten:
with open("myfile", "rb") as f:
while True:
byte = f.read(1)
if not byte:
break
do_stuff_with(ord(byte))
Für Python-Versionen 2.6 und höher, weil:
Oder verwenden Sie die JF Sebastians-Lösung für eine verbesserte Geschwindigkeit
from functools import partial
with open(filename, 'rb') as file:
for byte in iter(partial(file.read, 1), b''):
# Do stuff with byte
Oder wenn Sie es als Generatorfunktion haben möchten, wie von codeape demonstriert:
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)
Nachdem ich alles oben Genannte ausprobiert und die Antwort von @Aaron Hall verwendet hatte, wurden Speicherfehler für eine ~ 90-MB-Datei auf einem Computer mit Windows 10, 8 GB RAM und Python 3.5 32-Bit angezeigt. Ich wurde von einem Kollegen empfohlen, numpy
stattdessen zu verwenden, und es wirkt Wunder.
Bei weitem ist das Lesen einer gesamten Binärdatei (die ich getestet habe) am schnellsten:
import numpy as np
file = "binary_file.bin"
data = np.fromfile(file, 'u1')
Viele schneller als alle anderen Methoden bisher. Hoffe es hilft jemandem!
numpy
, dann könnte es sich lohnen.
Wenn Sie viele Binärdaten lesen müssen, sollten Sie das Strukturmodul in Betracht ziehen . Es wird dokumentiert, dass "zwischen C- und Python-Typen" konvertiert wird, aber natürlich sind Bytes Bytes, und es spielt keine Rolle, ob diese als C-Typen erstellt wurden. Wenn Ihre Binärdaten beispielsweise zwei 2-Byte-Ganzzahlen und eine 4-Byte-Ganzzahl enthalten, können Sie diese wie folgt lesen (Beispiel aus der struct
Dokumentation):
>>> struct.unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')
(1, 2, 3)
Möglicherweise ist dies bequemer, schneller oder beides, als den Inhalt einer Datei explizit zu durchlaufen.
Dieser Beitrag selbst ist keine direkte Antwort auf die Frage. Stattdessen handelt es sich um einen datengesteuerten erweiterbaren Benchmark, mit dem viele der Antworten (und Variationen der Verwendung neuer Funktionen, die in späteren, moderneren Versionen von Python hinzugefügt wurden) verglichen werden können, die zu dieser Frage veröffentlicht wurden - und daher sollten Seien Sie hilfreich bei der Ermittlung der besten Leistung.
In einigen Fällen habe ich den Code in der Antwort, auf die verwiesen wird, geändert, um ihn mit dem Benchmark-Framework kompatibel zu machen.
Hier sind zunächst die Ergebnisse für die aktuellsten Versionen von 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
Ich habe es auch mit einer viel größeren 10-MiB-Testdatei (deren Ausführung fast eine Stunde dauerte) ausgeführt und Leistungsergebnisse erhalten, die mit den oben gezeigten vergleichbar waren.
Hier ist der Code für das Benchmarking:
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
stattdessen tue for byte in chunk: yield byte
? Ich denke, ich sollte meine Antwort damit verschärfen.
yield from
.
enumerate
da die Iteration so verstanden werden sollte, dass sie abgeschlossen ist - wenn nicht, habe ich sie zuletzt überprüft -, hat die Aufzählung einen gewissen Aufwand mit Kosten für die Buchhaltung für den Index mit + = 1, sodass Sie alternativ die Buchhaltung in Ihrem durchführen können eigener Code. Oder gehen Sie sogar zu einem Deque mit maxlen=0
.
enumerate
. Danke für die Rückmeldung. Fügt meinem Beitrag ein Update hinzu, das es nicht enthält (obwohl ich nicht denke, dass es die Ergebnisse stark verändert). Wird auch die numpy
Antwort von @Rick M. hinzufügen .
super().
anstelle von tuple.
in verwenden __new__
, könnten Sie die namedtuple
Attributnamen anstelle von Indizes verwenden.
Wenn Sie nach etwas Schnellem suchen, ist hier eine Methode, die ich seit Jahren verwende:
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)
Wenn Sie Zeichen anstelle von Ints iterieren möchten, können Sie einfach data = file.read()
das Objekt bytes () in py3 verwenden.