Lire les blocs d'un objet fichier jusqu'à x octets de la fin

Nov 22 2020

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 tagmé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 un io.BytesIO()objet, ou pour un fichier ouvert SFTP distant (avec pysftp).

  • 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

1 LiorCohen Nov 22 2020 at 20:31

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.

1 PresidentJamesK.Polk Nov 23 2020 at 00:16

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 bufjusqu'à EOS, puis en traitant finalement les 16 derniers. Notez que s'il n'y a pas au moins 17 octets dedans bufalors buf[:-16]renvoie l'objet octets vide.