Lesen Sie Blöcke von einem Dateiobjekt bis x Bytes vom Ende
Ich muss Blöcke von 64 KB in einer Schleife lesen und verarbeiten, aber am Ende der Datei minus 16 Bytes anhalten : Die letzten 16 Bytes sind tag
Metadaten.
Die Datei ist möglicherweise sehr groß, daher kann ich nicht alles im RAM lesen.
Alle Lösungen, die ich finde, sind etwas ungeschickt und / oder unpythonisch.
with open('myfile', 'rb') as f:
while True:
block = f.read(65536)
if not block:
break
process_block(block)
Wenn ja 16 <= len(block) < 65536
, ist es einfach: Es ist der letzte Block überhaupt. Also useful_data = block[:-16]
undtag = block[-16:]
Wenn len(block) == 65536
, könnte es drei Dinge bedeuten: dass der vollständige Block nützliche Daten sind. Oder dass dieser 64-KB-Block tatsächlich der letzte Block ist , also useful_data = block[:-16]
und tag = block[-16:]
. Oder dass auf diesen 64-KB-Block ein weiterer Block mit nur wenigen Bytes (sagen wir 3 Bytes) folgt, also in diesem Fall: useful_data = block[:-13]
und tag = block[-13:] + last_block[:3]
.
Wie kann man mit diesem Problem besser umgehen, als all diese Fälle zu unterscheiden?
Hinweis:
Die Lösung sollte für eine Datei funktionieren, die mit geöffnet wurde
open(...)
, aber auch für einio.BytesIO()
Objekt oder für eine entfernte geöffnete SFTP-Datei (mitpysftp
).Ich habe darüber nachgedacht, die Größe des Dateiobjekts mit zu ermitteln
f.seek(0,2) length = f.tell() f.seek(0)
Dann nach jedem
block = f.read(65536)
wir können wissen, ob wir weit vom Ende entfernt sind
length - f.tell()
, aber auch hier sieht die vollständige Lösung nicht sehr elegant aus.
Antworten
Sie können einfach jede Iteration einlesen min(65536, L-f.tell()-16)
Etwas wie das:
from pathlib import Path
L = Path('myfile').stat().st_size
with open('myfile', 'rb') as f:
while True:
to_read_length = min(65536, L-f.tell()-16)
block = f.read(to_read_length)
process_block(block)
if f.tell() == L-16
break
Ich habe das nicht ausgeführt, aber ich hoffe, Sie bekommen den Kern davon.
Die folgende Methode basiert nur auf der Tatsache, dass die f.read()
Methode am Ende des Streams (EOS) ein leeres Byte-Objekt zurückgibt. Es könnte somit für Steckdosen einfach durch Ersetzen f.read()
durch übernommen werden s.recv()
.
def read_all_but_last16(f):
rand = random.Random() # just for testing
buf = b''
while True:
bytes_read = f.read(rand.randint(1, 40)) # just for testing
# bytes_read = f.read(65536)
buf += bytes_read
if not bytes_read:
break
process_block(buf[:-16])
buf = buf[-16:]
verify(buf[-16:])
Es funktioniert durch die immer 16 Bytes am Ende verlassen , buf
bis EOS, dann schließlich die Verarbeitung des letzten 16 Beachten Sie, dass , wenn es nicht mindestens 17 Bytes sind in buf
dann buf[:-16]
gibt das leere Bytes Objekt.