2.29. Struct et données binaires¶
Le module struct regroupe des valeurs Python dans une disposition binaire de taille fixe et décode des octets pour retrouver des valeurs Python. Utilisez-le lorsque vous travaillez avec un format de fichier binaire, un protocole réseau ou un périphérique qui échange des enregistrements de taille fixe.
Deux fonctions couvrent la plupart des cas :
struct.pack()– prend des valeurs Python et une chaîne de format, et renvoie un objetbytesayant exactement la disposition voulue.struct.unpack()– prend une chaîne de format et un objetbytes, et renvoie un tuple de valeurs Python.
2.29.1. Chaînes de format¶
Une chaîne de format liste un code par champ de l’enregistrement. Les codes décrivent à la fois la taille et l’interprétation de chaque champ.
Le type int de Python n’a pas de taille fixe – il s’agrandit pour contenir n’importe quelle valeur que vous lui affectez. Les formats binaires, eux, ont une taille fixe : chaque champ entier utilise un nombre d’octets convenu. struct effectue la conversion entre les entiers Python non bornés et ces représentations de taille fixe.
La largeur d’un entier est le nombre de bits qu’il utilise. Un octet vaut huit bits. Le code en minuscule est la variante signée ; le code en majuscule est la variante non signée (uniquement des valeurs positives ou nulles) :
b/B– 8 bits (un octet).-128..127signé,0..255non signé.h/H– 16 bits (deux octets).-32768..32767signé,0..65535non signé.i/I– 32 bits (quatre octets). Environ ±deux milliards en signé, quatre milliards en non signé.q/Q– 64 bits (huit octets). Effectivement non borné pour un usage courant.
Choisissez une largeur qui couvre confortablement la plage que vous attendez. Empaqueter une valeur en dehors de la plage déclarée entraîne soit un débordement silencieux par enroulement, soit la levée de struct.error, selon la version compilée.
Les autres codes courants concernent les flottants et les chaînes d’octets :
f– flottant 32 bits (simple précision ; environ sept chiffres décimaux). Le typefloatstandard de Python sur MicroPython a déjà cette taille, donc en empaqueter un dansfse fait sans perte.d– flottant 64 bits (double précision ; environ quinze chiffres décimaux). Empaqueter unfloatMicroPython de 32 bits dansdl’élargit à huit octets mais n’ajoute aucune précision.s– chaîne d’octets de longueur fixe, précédée d’un compteur (8spour un champ de huit octets).
2.29.2. Ordre des octets¶
Un entier multi-octets peut être stocké en mémoire de deux façons. Le nombre 0x12345678 dans un champ de 32 bits est disposé ainsi :
Petit-boutiste (little-endian) – l’octet de poids faible en premier :
78 56 34 12.Gros-boutiste (big-endian) – l’octet de poids fort en premier :
12 34 56 78.
Les deux encodent la même valeur ; ils diffèrent seulement sur l’extrémité du champ où se trouve l’octet de poids faible. Un fichier écrit par un système est illisible lorsqu’il est lu par l’autre si l’ordre des octets ne correspond pas.
Le premier caractère de la chaîne de format choisit l’ordre :
<– petit-boutiste. Courant sur x86 et ARM.>– gros-boutiste. Courant dans les protocoles réseau.!– ordre réseau, équivalent à>.
Sans caractère de tête, l’ordre des octets natif et l’alignement natif sont utilisés ; spécifier explicitement < ou > lève cette ambiguïté et c’est généralement ce que vous voulez lors de la lecture d’un fichier ou de la communication avec une autre machine.
Note
L’OpenMV Cam est petit-boutiste – comme son PC hôte. Utilisez < dans les chaînes de format pour les fichiers locaux à la caméra et pour les données binaires qui transitent depuis ou vers un ordinateur de bureau. Utilisez > (ou !) pour les protocoles réseau et pour tout format dont la spécification exige le gros-boutiste.
"<HI" empaquette une valeur de 16 bits suivie d’une valeur de 32 bits en six octets petit-boutistes.¶
2.29.3. Empaquetage¶
import struct
blob = struct.pack("<HI", 320, 1000000)
print(blob, len(blob))
Sortie
b'@\x01@B\x0f\x00' 6
Le format <HI produit six octets : deux pour le champ H et quatre pour le champ I, le tout en petit-boutiste. Passez exactement le nombre de valeurs attendu par le format – une incohérence lève struct.error.
2.29.4. Décodage¶
width, count = struct.unpack("<HI", blob)
print(width, count)
Sortie
320 1000000
struct.unpack() renvoie toujours un tuple, même lorsque le format ne décrit qu’un seul champ. Décodez-le sur la même ligne pour plus de lisibilité.
2.29.5. Chaînes d’octets de longueur fixe¶
Le code s lit ou écrit un bloc d’octets tel quel. Le compteur se place avant le s – 4s signifie « quatre octets traités comme une seule chaîne d’octets ». C’est la manière habituelle d’intégrer une valeur magique, une étiquette de taille fixe ou un champ de nom complété dans un enregistrement :
header = struct.pack("<4sHI", b"OMV0", 320, 1000000)
print(header)
Sortie
b'OMV0@\x01@B\x0f\x00'
Les quatre premiers octets sont la valeur magique littérale b"OMV0" ; les deux suivants sont le champ H (320) ; les quatre derniers sont le champ I (1000000). Le décodage renvoie les octets sous forme d’un objet bytes :
magic, width, count = struct.unpack("<4sHI", header)
print(magic, width, count)
Sortie
b'OMV0' 320 1000000
Si la valeur source est plus courte que le compteur déclaré, le résultat est complété à droite par des \x00 ; si elle est plus longue, les octets excédentaires sont silencieusement supprimés :
struct.pack("4s", b"hi") # b'hi\x00\x00'
struct.pack("4s", b"toolong") # b'tool'
Le compteur est une longueur en octets, et non un nombre de caractères – s manipule des octets bruts, donc une chaîne UTF-8 contenant des caractères multi-octets doit d’abord être encodée avec .encode() et comptée en octets.
2.29.6. Dimensionnement et lectures partielles¶
struct.calcsize() renvoie le nombre d’octets consommés par une chaîne de format :
struct.calcsize("<HI") # 6
Lors de la lecture d’un flux d’enregistrements depuis un fichier, lisez exactement ce nombre d’octets par enregistrement :
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)
Une lecture incomplète en fin de fichier produit un bloc plus petit que record_size – considérez cela comme la condition de fin de flux plutôt que de tenter de décoder un enregistrement partiel.