Lire les blocs d'un objet fichier jusqu'à x octets de la fin
J'ai besoin de lire des morceaux de 64 Ko en boucle et de les traiter, mais je m'arrête à la fin du fichier moins 16 octets : les 16 derniers octets sont des tag
métadonnées.
Le fichier est peut-être très volumineux, je ne peux donc pas tout lire dans la RAM.
Toutes les solutions que je trouve sont un peu maladroites et / ou impythoniques.
with open('myfile', 'rb') as f:
while True:
block = f.read(65536)
if not block:
break
process_block(block)
Si 16 <= len(block) < 65536
, c'est facile: c'est le dernier bloc de tous les temps. Alors useful_data = block[:-16]
ettag = block[-16:]
Si len(block) == 65536
, cela pourrait signifier trois choses: que le bloc complet soit des données utiles. Ou que ce bloc de 64 Ko est en fait le dernier bloc , ainsi useful_data = block[:-16]
et tag = block[-16:]
. Ou que ce bloc de 64 Ko est suivi d'un autre bloc de seulement quelques octets (disons 3 octets), donc dans ce cas: useful_data = block[:-13]
et tag = block[-13:] + last_block[:3]
.
Comment traiter ce problème de manière plus agréable que de distinguer tous ces cas?
Remarque:
la solution devrait fonctionner pour un fichier ouvert avec
open(...)
, mais aussi pour unio.BytesIO()
objet, ou pour un fichier ouvert SFTP distant (avecpysftp
).Je pensais obtenir la taille de l'objet fichier, avec
f.seek(0,2) length = f.tell() f.seek(0)
Puis après chaque
block = f.read(65536)
on peut savoir si on est loin de la fin avec
length - f.tell()
, mais là encore la solution complète n'a pas l'air très élégante.
Réponses
vous pouvez simplement lire à chaque itération min(65536, L-f.tell()-16)
Quelque chose comme ça:
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
Je n'ai pas couru cela, mais j'espère que vous en comprenez l'essentiel.
La méthode suivante repose uniquement sur le fait que la f.read()
méthode renvoie un objet bytes vide à la fin du flux (EOS). Il pourrait donc être adopté pour les sockets simplement en le remplaçant f.read()
par 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:])
Il fonctionne en laissant toujours 16 octets à la fin de buf
jusqu'à EOS, puis en traitant finalement les 16 derniers. Notez que s'il n'y a pas au moins 17 octets dedans buf
alors buf[:-16]
renvoie l'objet octets vide.