Czytaj bloki z obiektu pliku do x bajtów od końca

Nov 22 2020

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 tagmetadane.

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) < 65536to 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 dla io.BytesIO()obiektu lub dla odległego otwartego pliku SFTP (z pysftp).

  • 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

1 LiorCohen Nov 22 2020 at 20:31

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.

1 PresidentJamesK.Polk Nov 23 2020 at 00:16

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 bufdo EOS, a następnie ostatecznie przetwarzając ostatnie 16. Zauważ, że jeśli nie ma co najmniej 17 bajtów, bufto buf[:-16]zwraca pusty obiekt bajtów.