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 / B – 8-bitni (jedan bajt). -128..127 predznačeno, 0..255 nepredznačeno.

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

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

  • q / Q – 64-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.

Six bytes laid out in a row, with the first two bytes grouped as an "H" field (16-bit unsigned) and the next four as an "I" field (32-bit unsigned), each labelled with their little-endian byte order.

"<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 s – 4s 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.