2.29. Struct i binarni podaci

Modul struct pakira Python vrijednosti u fiksni binarni raspored i raspakira bajtove natrag u Python vrijednosti. Posegnite za njim kada radite s binarnim formatom datoteke, mrežnim protokolom ili uređajem koji razmjenjuje zapise fiksne veličine.

Dvije funkcije pokrivaju većinu slučajeva:

  • struct.pack() – uzima Python vrijednosti i format string, vraća bytes objekt točno zadanog rasporeda.

  • struct.unpack() – uzima format string i bytes objekt, vraća n-torku Python vrijednosti.

2.29.1. Format stringovi

Format string navodi po jedan kod za svako polje u zapisu. Kodovi opisuju i veličinu i interpretaciju svakog polja.

Python int nema fiksnu veličinu – raste kako bi stao u bilo koju vrijednost koju mu dodijelite. Binarni formati imaju fiksne veličine: svako cjelobrojno polje koristi dogovoreni broj bajtova. struct pretvara između neograničenih Python cijelih brojeva i tih prikaza fiksne veličine.

Širina cijelog broja je broj bitova koje koristi. Jedan bajt je osam bitova. Mali tiskani kod je predznačena varijanta; veliki tiskani kod je nepredznačena (samo nenegativne vrijednosti):

  • b / B8-bitni (jedan bajt). -128..127 predznačeno, 0..255 nepredznačeno.

  • h / H16-bitni (dva bajta). -32768..32767 predznačeno, 0..65535 nepredznačeno.

  • i / I32-bitni (četiri bajta). Otprilike ±dvije milijarde predznačeno, četiri milijarde nepredznačeno.

  • q / Q64-bitni (osam bajtova). Za svakodnevnu uporabu praktički neograničeno.

Odaberite širinu koja udobno pokriva raspon koji očekujete. Pakiranje vrijednosti izvan deklariranog raspona ili tiho prelije (wrap around) ili podigne struct.error, ovisno o buildu.

Preostali uobičajeni kodovi su za brojeve s pomičnim zarezom i nizove bajtova:

  • f – 32-bitni broj s pomičnim zarezom (jednostruka preciznost; oko sedam decimalnih znamenki). Uobičajeni Python float na MicroPythonu već je ove veličine, pa je pakiranje u f bez gubitaka.

  • d – 64-bitni broj s pomičnim zarezom (dvostruka preciznost; oko petnaest decimalnih znamenki). Pakiranje 32-bitnog MicroPython float u d proširuje ga na osam bajtova, ali ne dodaje preciznost.

  • s – niz bajtova fiksne duljine, kojem prethodi broj (8s za polje od osam bajtova).

2.29.2. Redoslijed bajtova

Višebajtni cijeli broj može se pohraniti u memoriju na dva načina. Broj 0x12345678 u 32-bitnom polju raspoređen je ovako:

  • Little-endian – najmanje značajan bajt prvi: 78 56 34 12.

  • Big-endian – najznačajniji bajt prvi: 12 34 56 78.

Oba kodiraju istu vrijednost; razlikuju se samo u tome koji kraj polja sadrži niski bajt. Datoteku koju je napisao jedan sustav drugi sustav iščita iskrivljeno ako se redoslijed bajtova ne podudara.

Vodeći znak format stringa odabire redoslijed:

  • < – little-endian. Uobičajen na x86 i ARM.

  • > – big-endian. Uobičajen u mrežnim protokolima.

  • ! – mrežni redoslijed, ekvivalentan >.

Bez vodećeg znaka koriste se nativni redoslijed bajtova i nativno poravnanje; eksplicitno postavljanje < ili > uklanja tu nejednoznačnost i obično je ono što želite pri čitanju datoteke ili komunikaciji s drugim računalom.

Napomena

OpenMV Cam je little-endian – isto kao i njegovo host računalo. Koristite < u format stringovima za datoteke lokalne kameri i za binarne podatke koji putuju prema ili od stolnog računala. Koristite > (ili !) za mrežne protokole i za svaki format čija specifikacija zahtijeva big-endian.

Šest bajtova raspoređenih u nizu, pri čemu su prva dva bajta grupirana kao polje "H" (16-bitno nepredznačeno), a sljedeća četiri kao polje "I" (32-bitno nepredznačeno), svako označeno svojim little-endian redoslijedom bajtova.

"<HI" pakira 16-bitnu vrijednost iza koje slijedi 32-bitna vrijednost u šest little-endian bajtova.

2.29.3. Pakiranje

import struct

blob = struct.pack("<HI", 320, 1000000)
print(blob, len(blob))

Ispis:

b'@\x01@B\x0f\x00' 6

Format <HI proizvodi šest bajtova: dva za polje H i četiri za polje I, sve little-endian. Proslijedite točno onaj broj vrijednosti koji format očekuje – nepodudaranje podiže struct.error.

2.29.4. Raspakiranje

width, count = struct.unpack("<HI", blob)
print(width, count)

Ispis:

320 1000000

struct.unpack() uvijek vraća n-torku, čak i kada format opisuje jedno polje. Raspakirajte je u istom retku radi čitljivosti.

2.29.5. Nizovi bajtova fiksne duljine

Kod s čita ili piše komad bajtova doslovno. Broj ide ispred s4s znači „četiri bajta tretirana kao jedan niz bajtova”. Ovo je uobičajen način ugradnje magične vrijednosti, oznake (tag) fiksne veličine ili dopunjenog polja imena u zapis:

header = struct.pack("<4sHI", b"OMV0", 320, 1000000)
print(header)

Ispis:

b'OMV0@\x01@B\x0f\x00'

Prva četiri bajta su doslovna magična vrijednost b"OMV0"; sljedeća dva su polje H (320); posljednja četiri su polje I (1000000). Raspakiranje vraća bajtove natrag kao bytes objekt:

magic, width, count = struct.unpack("<4sHI", header)
print(magic, width, count)

Ispis:

b'OMV0' 320 1000000

Ako je izvorna vrijednost kraća od deklariranog broja, rezultat se s desne strane dopunjuje s \x00; ako je dulja, višak bajtova se tiho odbacuje:

struct.pack("4s", b"hi")        # b'hi\x00\x00'
struct.pack("4s", b"toolong")   # b'tool'

Broj je duljina u bajtovima, a ne broj znakova – s radi sa sirovim bajtovima, pa UTF-8 niz s višebajtnim znakovima prvo treba .encode()-ati i prebrojati u bajtovima.

2.29.6. Određivanje veličine i djelomična čitanja

struct.calcsize() vraća broj bajtova koje format string troši:

struct.calcsize("<HI")     # 6

Pri čitanju toka zapisa iz datoteke, pročitajte točno toliko bajtova po zapisu:

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)

Kratko čitanje na kraju datoteke proizvodi komad manji od record_size – tretirajte to kao uvjet kraja toka umjesto da pokušavate raspakirati djelomičan zapis.