Wie lese ich in Python eine Binärdatei ein und durchlaufe jedes Byte dieser Datei?
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:
Informationen zu Iteratoren und Generatoren finden Sie in der Python-Dokumentation .
quelle
8192 Byte = 8 kB
(eigentlich ist esKiB
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 wirdyield from chunk
. Diese Form vonyield
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:
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:
Die
with
Anweisung ist in Python 2.5 und höher verfügbar.quelle
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 :Es ruft auf,
file.read(1)
bis es nichts zurückgibtb''
(leerer Bytestring). Der Speicher wächst nicht unbegrenzt für große Dateien. Sie könnenbuffering=0
an übergebenopen()
, 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:Beispiel:
Es verarbeitet ~ 1,5 GB / s , wenn
chunksize == 32768
auf meiner Maschine und nur ~ 7,5 MB / s beichunksize == 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 alsbytearray
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 einfachefor
Schleife verwenden:mmap
unterstützt die Slice-Notation. Beispielsweisemm[i:i+len]
kehrtlen
Bytes aus der Datei , beginnend an Positioni
. Das Kontextmanagerprotokoll wird vor Python 3.2 nicht unterstützt. Sie müssenmm.close()
in diesem Fall explizit aufrufen . Das Durchlaufen jedes Bytes mitmmap
verbraucht mehr Speicher alsfile.read(1)
, ist jedochmmap
um eine Größenordnung schneller.quelle
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.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: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):
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 dercallable, sentinel
Signatur - der Python 2-Version - aufteilen:(Einige andere Antworten erwähnen dies, aber nur wenige bieten eine vernünftige Lesegröße.)
Best Practice für große Dateien oder gepuffertes / interaktives Lesen
Erstellen wir dazu eine Funktion, einschließlich der idiomatischen Verwendung der Standardbibliothek für Python 3.5+:
Beachten Sie, dass wir verwenden
file.read1
.file.read
blockiert, bis alle angeforderten Bytes oderEOF
.file.read1
ermöglicht es uns, das Blockieren zu vermeiden, und es kann dadurch schneller zurückkehren. Keine anderen Antworten erwähnen dies ebenfalls.Demonstration der Verwendung von Best Practices:
Lassen Sie uns eine Datei mit einem Megabyte (eigentlich Mebibyte) pseudozufälliger Daten erstellen:
Lassen Sie uns nun darüber iterieren und es im Gedächtnis materialisieren:
Wir können jeden Teil der Daten untersuchen, zum Beispiel die letzten 100 und die ersten 100 Bytes:
Bei Binärdateien nicht durch Zeilen iterieren
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:
Das Obige ist nur für semantisch lesbare Textdateien (wie Klartext, Code, Markup, Markdown usw.) geeignet, die Sie ohne das
'b'
Flag öffnen sollten .quelle
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 demopen
eingebauten Programm erreichen, aber es gibt viele Vorteile beim Schreiben des Programms, damit der Programmierer dasPath
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:
Für Python-Versionen 2.6 und höher, weil:
Oder verwenden Sie die JF Sebastians-Lösung für eine verbesserte Geschwindigkeit
Oder wenn Sie es als Generatorfunktion haben möchten, wie von codeape demonstriert:
quelle
Python 3, lesen Sie die gesamte Datei auf einmal:
Sie können mit
data
Variablen alles iterieren, was Sie wollen .quelle
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:
Referenz
Viele schneller als alle anderen Methoden bisher. Hoffe es hilft jemandem!
quelle
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):Möglicherweise ist dies bequemer, schneller oder beides, als den Inhalt einer Datei explizit zu durchlaufen.
quelle
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:
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:
quelle
yield from chunk
stattdessen tuefor 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 mitmaxlen=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 dienumpy
Antwort von @Rick M. hinzufügen .super().
anstelle vontuple.
in verwenden__new__
, könnten Sie dienamedtuple
Attributnamen anstelle von Indizes verwenden.Wenn Sie nach etwas Schnellem suchen, ist hier eine Methode, die ich seit Jahren verwende:
Wenn Sie Zeichen anstelle von Ints iterieren möchten, können Sie einfach
data = file.read()
das Objekt bytes () in py3 verwenden.quelle