Czytaj bloki z obiektu pliku do x bajtów od końca
Muszę czytać w pętli fragmenty 64KB i przetwarzać je, ale zatrzymuję się na końcu pliku minus 16 bajtów : ostatnie 16 bajtów to tag
metadane.
Plik może być bardzo duży, więc nie mogę go odczytać w pamięci RAM.
Wszystkie znalezione przeze mnie rozwiązania są trochę niezdarne i / lub nieszpiegowskie.
with open('myfile', 'rb') as f:
while True:
block = f.read(65536)
if not block:
break
process_block(block)
Jeśli 16 <= len(block) < 65536
to proste: to ostatni blok na świecie. Więc useful_data = block[:-16]
itag = block[-16:]
Jeśli len(block) == 65536
, to mogłoby oznaczać trzy rzeczy: że pełny blok jest użyteczne dane. Albo że ten blok 64KB jest w rzeczywistości ostatnim blokiem , więc useful_data = block[:-16]
i tag = block[-16:]
. Lub że po tym bloku 64KB następuje kolejny blok o wielkości zaledwie kilku bajtów (powiedzmy 3 bajty), więc w tym przypadku: useful_data = block[:-13]
i tag = block[-13:] + last_block[:3]
.
Jak ładniej poradzić sobie z tym problemem niż rozróżnianie wszystkich tych przypadków?
Uwaga:
rozwiązanie powinno działać dla pliku otwartego za pomocą
open(...)
, ale także dlaio.BytesIO()
obiektu lub dla odległego otwartego pliku SFTP (zpysftp
).Myślałem o uzyskaniu rozmiaru obiektu pliku za pomocą
f.seek(0,2) length = f.tell() f.seek(0)
Następnie po każdym
block = f.read(65536)
możemy wiedzieć, czy jesteśmy daleko od końca
length - f.tell()
, ale znowu pełne rozwiązanie nie wygląda zbyt elegancko.
Odpowiedzi
możesz po prostu przeczytać w każdej iteracji min(65536, L-f.tell()-16)
Coś takiego:
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
Nie uruchomiłem tego, ale mam nadzieję, że zrozumiesz.
Poniższa metoda opiera się tylko na fakcie, że f.read()
metoda zwraca pusty obiekt bajtów po zakończeniu strumienia (EOS). W ten sposób można go zastosować do gniazd, po prostu zastępując f.read()
go 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:])
Działa zawsze pozostawiając 16 bajtów na końcu buf
do EOS, a następnie ostatecznie przetwarzając ostatnie 16. Zauważ, że jeśli nie ma co najmniej 17 bajtów, buf
to buf[:-16]
zwraca pusty obiekt bajtów.