2.29. Struct și date binare¶
Modulul struct împachetează valori Python într-un format binar fix și despachetează octeții înapoi în valori Python. Apelează la el atunci când lucrezi cu un format de fișier binar, un protocol de rețea sau un dispozitiv care schimbă înregistrări de dimensiune fixă.
Două funcții acoperă majoritatea cazurilor:
struct.pack()– preia valori Python și un șir de format, returnând un obiectbytescu exact acel format.struct.unpack()– preia un șir de format și un obiectbytes, returnând un tuplu de valori Python.
2.29.1. Șiruri de format¶
Un șir de format enumeră câte un cod pentru fiecare câmp din înregistrare. Codurile descriu atât dimensiunea, cât și interpretarea fiecărui câmp.
Tipul int din Python nu are o dimensiune fixă – crește pentru a încăpea orice valoare îi atribui. Formatele binare au dimensiuni fixe: fiecare câmp întreg folosește un număr convenit de octeți. struct face conversia între întregii Python nelimitați și aceste reprezentări de dimensiune fixă.
Lățimea unui întreg este numărul de biți pe care îi folosește. Un octet are opt biți. Codul cu literă mică este varianta cu semn; codul cu literă mare este cea fără semn (doar valori nenegative):
b/B– 8 biți (un octet).-128..127cu semn,0..255fără semn.h/H– 16 biți (doi octeți).-32768..32767cu semn,0..65535fără semn.i/I– 32 de biți (patru octeți). Aproximativ ±două miliarde cu semn, patru miliarde fără semn.q/Q– 64 de biți (opt octeți). Practic nelimitat pentru uzul zilnic.
Alege o lățime care acoperă confortabil intervalul pe care îl aștepți. Împachetarea unei valori în afara intervalului declarat fie se reia silențios prin depășire (wrap-around), fie generează struct.error, în funcție de build.
Celelalte coduri uzuale sunt pentru numere în virgulă mobilă și șiruri de octeți:
f– float pe 32 de biți (precizie simplă; aproximativ șapte cifre zecimale). Tipul obișnuitfloatdin MicroPython are deja această dimensiune, deci împachetarea lui într-unfse face fără pierderi.d– float pe 64 de biți (precizie dublă; aproximativ cincisprezece cifre zecimale). Împachetarea unuifloatde 32 de biți din MicroPython într-undîl extinde la opt octeți, dar nu adaugă precizie.s– șir de octeți de lungime fixă, precedat de un contor (8spentru un câmp de opt octeți).
2.29.2. Ordinea octeților¶
Un întreg pe mai mulți octeți poate fi stocat în memorie în două moduri. Numărul 0x12345678 într-un câmp pe 32 de biți este dispus astfel:
Little-endian – octetul cel mai puțin semnificativ primul:
78 56 34 12.Big-endian – octetul cel mai semnificativ primul:
12 34 56 78.
Ambele codifică aceeași valoare; diferă doar prin capătul câmpului la care se află octetul inferior. Un fișier scris de un sistem apare deformat la citirea de către celălalt dacă ordinea octeților nu se potrivește.
Caracterul de la începutul șirului de format alege ordinea:
<– little-endian. Frecvent pe x86 și ARM.>– big-endian. Frecvent în protocoalele de rețea.!– ordinea de rețea, echivalentă cu>.
Fără un caracter inițial, se folosesc ordinea nativă a octeților și alinierea nativă; setarea explicită a lui < sau > elimină acea ambiguitate și este de obicei ceea ce îți dorești atunci când citești un fișier sau comunici cu o altă mașină.
Notă
OpenMV Cam este little-endian – la fel ca PC-ul gazdă. Folosește < în șirurile de format pentru fișierele locale ale camerei și pentru datele binare care circulă către sau dinspre un desktop. Folosește > (sau !) pentru protocoalele de rețea și pentru orice format a cărui specificație impune big-endian.
"<HI" împachetează o valoare pe 16 biți urmată de o valoare pe 32 de biți în șase octeți little-endian.¶
2.29.3. Împachetare¶
import struct
blob = struct.pack("<HI", 320, 1000000)
print(blob, len(blob))
Ieșire:
b'@\x01@B\x0f\x00' 6
Formatul <HI produce șase octeți: doi pentru câmpul H și patru pentru câmpul I, toți little-endian. Transmite exact numărul de valori pe care îl așteaptă formatul – o nepotrivire generează struct.error.
2.29.4. Despachetare¶
width, count = struct.unpack("<HI", blob)
print(width, count)
Ieșire:
320 1000000
struct.unpack() returnează întotdeauna un tuplu, chiar și atunci când formatul descrie un singur câmp. Despachetează-l pe aceeași linie pentru lizibilitate.
2.29.5. Șiruri de octeți de lungime fixă¶
Codul s citește sau scrie o secvență de octeți ca atare. Contorul se pune înaintea lui s – 4s înseamnă „patru octeți tratați ca un singur șir de octeți”. Acesta este modul obișnuit de a încorpora o valoare magică, o etichetă de dimensiune fixă sau un câmp de nume completat cu spațiu într-o înregistrare:
header = struct.pack("<4sHI", b"OMV0", 320, 1000000)
print(header)
Ieșire:
b'OMV0@\x01@B\x0f\x00'
Primii patru octeți sunt valoarea magică literală b"OMV0"; următorii doi sunt câmpul H (320); ultimii patru sunt câmpul I (1000000). Despachetarea returnează octeții înapoi ca obiect bytes:
magic, width, count = struct.unpack("<4sHI", header)
print(magic, width, count)
Ieșire:
b'OMV0' 320 1000000
Dacă valoarea sursă este mai scurtă decât contorul declarat, rezultatul este completat la dreapta cu \x00; dacă este mai lungă, octeții în plus sunt eliminați silențios:
struct.pack("4s", b"hi") # b'hi\x00\x00'
struct.pack("4s", b"toolong") # b'tool'
Contorul este o lungime în octeți, nu un număr de caractere – s operează cu octeți bruți, așa că un șir UTF-8 cu caractere pe mai mulți octeți trebuie mai întâi codificat cu .encode() și contorizat în octeți.
2.29.6. Dimensionare și citiri parțiale¶
struct.calcsize() returnează numărul de octeți pe care îi consumă un șir de format:
struct.calcsize("<HI") # 6
Când citești un flux de înregistrări dintr-un fișier, citește exact acel număr de octeți pentru fiecare înregistrare:
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)
O citire incompletă la sfârșitul fișierului produce o secvență mai mică decât record_size – tratează acest lucru ca pe condiția de sfârșit al fluxului, în loc să încerci să despachetezi o înregistrare parțială.