struct - Lavorare con dati binari

Scopo Converte tra stringhe e dati binari
Versione Python 1.4 e successiva

Il modulo struct include le funzioni per la conversione tra stringhe di byte e tipi di dati nativi di Python come numeri e stringhe.

Le Funzioni contro la Classe Struct

Ci sono un insieme di funzioni a livello modulo per lavorare con valori strutturati, e c'è anche la classe Struct (nuova in Python 2.5). Gli specificatori di formato sono convertiti dal loro formato stringa ad una rappresentazione compilata, simile al modo in cui sono compilate le espressioni regolari. La conversione richiede delle risorse, quindi in genere è più efficiente farlo una volta sola quando si crea l'istanza di Struct, quindi chiamare i metodi dell'istanza invece che usare le funzioni a livello modulo. Tutti gli esempi seguenti usano la classe Struct.

Packing e Unpacking

Struct supporta il packing di dati in stringhe e l'unpacking di dati dalle stringhe usando specificatori di formattazione costituiti da caratteri che rappresentano il tipo di dati ed indicatori di conteggio ed endian-ness opzionali. Per un completo dettaglio fare riferimento alla documentazione della libreria standard.

In questo esempio lo specificatore di formato richiede un valore intero o long, una stringa di due caratteri ed un numero a virgola mobile. Gli spazi tra gli specificatori di formato sono qui inclusi per chiarezza, e sono ignorati quando la formattazione viene compilata.

import struct
import binascii

values = (1, 'ab', 2.7)
s = struct.Struct('I 2s f')
packed_data = s.pack(*values)

print 'Valori originali     :', values
print 'Stringa formattazione:', s.format
print 'Usa                  :', s.size, 'byte'
print 'Valore Packed        :', binascii.hexlify(packed_data)
$ python example_pack.py

Valori originali     : (1, 'ab', 2.7000000000000002)
Stringa formattazione: I 2s f
Usa                  : 12 byte
Valore Packed        : 0100000061620000cdcc2c40

Il valore packed viene convertito in una sequenza di byte in notazione esadecimale per la stampa, visto che alcuni dei caratteri sono null.

Se si passa il valore packed ad unpack(), riotteniamo di fatto gli stessi valori originali (notare la discrepanza nel valore a virgola mobile).

import struct
import binascii

packed_data = binascii.unhexlify('0100000061620000cdcc2c40')

s = struct.Struct('I 2s f')
unpacked_data = s.unpack(packed_data)
print 'Valori Unpacked:', unpacked_data
$ python example_unpack.py

Valori Unpacked: (1, 'ab', 2.7000000476837158)

Endianness

Nel modo predefinito i valori sono codificati usando la nozione nativa della libreria di "endianness". E' semplice sovrascrivere quella scelta fornendo una direttiva esplicita di endianness nella stringa di formattazione.

import struct
import binascii

values = (1, 'ab', 2.7)
print 'Valori originali:', values

endianness = [
    ('@', 'native, native'),
    ('=', 'native, standard'),
    ('<', 'little-endian'),
    ('>', 'big-endian'),
    ('!', 'network'),
    ]

for code, name in endianness:
    s = struct.Struct(code + ' I 2s f')
    packed_data = s.pack(*values)
    print
    print 'Formato stringa  :', s.format, 'per', name
    print 'Usa              :', s.size, 'byte'
    print 'Valore Packed    :', binascii.hexlify(packed_data)
    print 'Valore Unpacked  :', s.unpack(packed_data)
$ python struct_endianness.py
Valori originali: (1, 'ab', 2.7000000000000002)

Formato stringa: @ I 2s f per native, native
Usa            : 12 byte
Valore packed  : 0100000061620000cdcc2c40
Valore unpacked: (1, 'ab', 2.7000000476837158)

Formato stringa: = I 2s f per native, standard
Usa            : 10 byte
Valore packed  : 010000006162cdcc2c40
Valore unpacked: (1, 'ab', 2.7000000476837158)

Formato stringa: < I 2s f per little-endian
Usa            : 10 byte
Valore packed  : 010000006162cdcc2c40
Valore unpacked: (1, 'ab', 2.7000000476837158)

Formato stringa: > I 2s f per big-endian
Usa            : 10 byte
Valore packed  : 000000016162402ccccd
Valore unpacked: (1, 'ab', 2.7000000476837158)

Formato stringa: ! I 2s f per network
Usa            : 10 byte
Valore packed  : 000000016162402ccccd
Valore unpacked: (1, 'ab', 2.7000000476837158)

Buffer

Il lavorare con dati binari packed è in genere riservato per situazioni delicate ad alte prestazioni o per il passaggio di dati da/per moduli estensione. Un modo di ottimizzare è di evitare di allocare un nuovo buffer per ogni struttura packed. I metodi pack_into() e unpack_from() forniscono il supporto alla scrittura diretta a buffer pre-allocati.

import struct
import binascii

s = struct.Struct('I 2s f')
values = (1, 'ab', 2.7)
print 'Originali:', values

print
print 'buffer stringa ctypes'

import ctypes
b = ctypes.create_string_buffer(s.size)
print 'Prima   :', binascii.hexlify(b.raw)
s.pack_into(b, 0, *values)
print 'Dopo    :', binascii.hexlify(b.raw)
print 'Unpacked:', s.unpack_from(b, 0)

print
print 'array'

import array
a = array.array('c', '\0' * s.size)
print 'Prima   :', binascii.hexlify(a)
s.pack_into(a, 0, *values)
print 'Dopo    :', binascii.hexlify(a)
print 'Unpacked:', s.unpack_from(a, 0)

Vedere anche:

struct
La documentazione della libreria standard per questo modulo.
array
Il modulo array, per lavorare con sequenze di valori di tipo fisso
binascii
Il modulo binascii per generare rappresentazioni ASCII di dati binari.
WidiPedia: Endianness
Spiegazione dell'ordine dei byte ed endianness nella codifica.


Vedere anche: