2.29. Struct e dati binari¶
Il modulo struct impacchetta i valori Python in un layout binario a dimensione fissa e ricostruisce i byte in valori Python. Tornalo utile quando lavori con un formato di file binario, un protocollo di rete o un dispositivo che scambia record di dimensione fissa.
Due funzioni coprono la maggior parte dei casi:
struct.pack()– prende valori Python e una stringa di formato e restituisce un oggettobytescon il layout esatto.struct.unpack()– prende una stringa di formato e un oggettobytese restituisce una tupla di valori Python.
2.29.1. Stringhe di formato¶
Una stringa di formato elenca un codice per ogni campo del record. I codici descrivono sia la dimensione sia l’interpretazione di ciascun campo.
Il tipo int di Python non ha dimensione fissa: cresce per adattarsi a qualunque valore gli assegni. I formati binari hanno invece dimensioni fisse: ogni campo intero usa un numero concordato di byte. struct converte tra gli int Python illimitati e queste rappresentazioni a dimensione fissa.
L”ampiezza di un intero è il numero di bit che utilizza. Un byte è composto da otto bit. Il codice minuscolo è la variante con segno; il codice maiuscolo è quella senza segno (solo valori non negativi):
b/B– 8 bit (un byte).-128..127con segno,0..255senza segno.h/H– 16 bit (due byte).-32768..32767con segno,0..65535senza segno.i/I– 32 bit (quattro byte). Circa ±due miliardi con segno, quattro miliardi senza segno.q/Q– 64 bit (otto byte). Di fatto illimitato per l’uso quotidiano.
Scegli un’ampiezza che copra comodamente l’intervallo che ti aspetti. Impacchettare un valore al di fuori dell’intervallo dichiarato provoca, a seconda della build, un wrap-around silenzioso oppure solleva struct.error.
I restanti codici comuni riguardano i float e le stringhe di byte:
f– float a 32 bit (precisione singola; circa sette cifre decimali). Il normalefloatdi Python su MicroPython ha già questa dimensione, quindi impacchettarne uno infnon comporta perdite.d– float a 64 bit (precisione doppia; circa quindici cifre decimali). Impacchettare unfloata 32 bit di MicroPython indlo allarga a otto byte ma non aggiunge precisione.s– stringa di byte a lunghezza fissa, preceduta da un conteggio (8sper un campo di otto byte).
2.29.2. Ordine dei byte¶
Un intero multi-byte può essere memorizzato in due modi. Il numero 0x12345678 in un campo a 32 bit viene disposto così:
Little-endian – prima il byte meno significativo:
78 56 34 12.Big-endian – prima il byte più significativo:
12 34 56 78.
Entrambi codificano lo stesso valore; si differenziano solo per quale estremità del campo contiene il byte basso. Un file scritto da un sistema risulta illeggibile quando viene letto da un altro se l’ordine dei byte non corrisponde.
Il carattere iniziale della stringa di formato sceglie l’ordine:
<– little-endian. Comune su x86 e ARM.>– big-endian. Comune nei protocolli di rete.!– ordine di rete, equivalente a>.
Senza un carattere iniziale vengono usati l’ordine dei byte e l’allineamento nativi; impostare esplicitamente < o > elimina questa ambiguità ed è di solito ciò che desideri quando leggi un file o comunichi con un’altra macchina.
Nota
La OpenMV Cam è little-endian, esattamente come il PC host. Usa < nelle stringhe di formato per i file locali della camera e per i dati binari che viaggiano da o verso un desktop. Usa > (oppure !) per i protocolli di rete e per qualsiasi formato la cui specifica richieda il big-endian.
"<HI" impacchetta un valore a 16 bit seguito da un valore a 32 bit in sei byte little-endian.¶
2.29.3. Impacchettamento¶
import struct
blob = struct.pack("<HI", 320, 1000000)
print(blob, len(blob))
Output:
b'@\x01@B\x0f\x00' 6
Il formato <HI produce sei byte: due per il campo H e quattro per il campo I, tutti little-endian. Passa esattamente il numero di valori che il formato si aspetta: una discrepanza solleva struct.error.
2.29.4. Spacchettamento¶
width, count = struct.unpack("<HI", blob)
print(width, count)
Output:
320 1000000
struct.unpack() restituisce sempre una tupla, anche quando il formato descrive un singolo campo. Spacchettala sulla stessa riga per maggiore leggibilità.
2.29.5. Stringhe di byte a lunghezza fissa¶
Il codice s legge o scrive un blocco di byte così com’è. Il conteggio va prima di s – 4s significa «quattro byte trattati come un’unica stringa di byte». È il modo consueto per incorporare in un record un valore magico, un tag a dimensione fissa o un campo nome con riempimento:
header = struct.pack("<4sHI", b"OMV0", 320, 1000000)
print(header)
Output:
b'OMV0@\x01@B\x0f\x00'
I primi quattro byte sono il valore magico letterale b"OMV0"; i due successivi sono il campo H (320); gli ultimi quattro sono il campo I (1000000). Lo spacchettamento restituisce i byte come oggetto bytes:
magic, width, count = struct.unpack("<4sHI", header)
print(magic, width, count)
Output:
b'OMV0' 320 1000000
Se il valore di origine è più corto del conteggio dichiarato, il risultato viene riempito a destra con \x00; se è più lungo, i byte in eccesso vengono scartati silenziosamente:
struct.pack("4s", b"hi") # b'hi\x00\x00'
struct.pack("4s", b"toolong") # b'tool'
Il conteggio è una lunghezza in byte, non un conteggio di caratteri: s opera su byte grezzi, quindi una stringa UTF-8 con caratteri multi-byte va prima sottoposta a .encode() e conteggiata in byte.
2.29.6. Dimensionamento e letture parziali¶
struct.calcsize() restituisce il numero di byte che una stringa di formato consuma:
struct.calcsize("<HI") # 6
Quando leggi un flusso di record da un file, leggi esattamente quel numero di byte per ciascun record:
record_size = struct.calcsize("<HI")
with open("data.bin", "rb") as f:
while True:
chunk = f.read(record_size)
if len(chunk) < record_size:
break
width, count = struct.unpack("<HI", chunk)
print(width, count)
Una lettura incompleta alla fine del file produce un blocco più piccolo di record_size: trattalo come condizione di fine flusso anziché tentare di spacchettare un record parziale.