2.29. Struct ja binääridata

struct-moduuli pakkaa Python-arvot kiinteään binäärimuotoon ja purkaa tavut takaisin Python-arvoiksi. Käytä sitä, kun työskentelet binääritiedostomuodon, verkkoprotokollan tai kiinteäkokoisia tietueita vaihtavan laitteen kanssa.

Kaksi funktiota kattaa useimmat tapaukset:

  • struct.pack() – ottaa Python-arvot ja muotoilumerkkijonon ja palauttaa täsmälleen oikean muotoisen bytes-objektin.

  • struct.unpack() – ottaa muotoilumerkkijonon ja bytes-objektin ja palauttaa Python-arvojen monikon.

2.29.1. Muotoilumerkkijonot

Muotoilumerkkijono luettelee yhden koodin kutakin tietueen kenttää kohti. Koodit kuvaavat sekä kunkin kentän koon että tulkinnan.

Pythonin int ei ole kiinteäkokoinen – se kasvaa sopimaan mihin tahansa arvoon, jonka sille annat. Binäärimuodot ovat kiinteäkokoisia: jokainen kokonaislukukenttä käyttää sovittua määrää tavuja. struct muuntaa rajoittamattomien Python-kokonaislukujen ja näiden kiinteäkokoisten esitysmuotojen välillä.

Kokonaisluvun leveys on bittien määrä, jonka se käyttää. Yksi tavu on kahdeksan bittiä. Pienaakkoskoodi on etumerkillinen muunnelma; suuraakkoskoodi on etumerkitön (vain ei-negatiiviset arvot):

  • b / B8-bittinen (yksi tavu). -128..127 etumerkillisenä, 0..255 etumerkittömänä.

  • h / H16-bittinen (kaksi tavua). -32768..32767 etumerkillisenä, 0..65535 etumerkittömänä.

  • i / I32-bittinen (neljä tavua). Noin ±kaksi miljardia etumerkillisenä, neljä miljardia etumerkittömänä.

  • q / Q64-bittinen (kahdeksan tavua). Käytännössä rajoittamaton arkikäytössä.

Valitse leveys, joka kattaa mukavasti odottamasi arvoalueen. Ilmoitetun arvoalueen ulkopuolisen arvon pakkaaminen joko kiertää hiljaisesti ympäri tai nostaa struct.error-poikkeuksen, riippuen käännösversiosta.

Loput yleiset koodit ovat liukulukuja ja tavumerkkijonoja varten:

  • f – 32-bittinen liukuluku (yksinkertainen tarkkuus; noin seitsemän desimaalinumeroa). Pythonin tavallinen float on MicroPythonissa jo tämänkokoinen, joten sellaisen pakkaaminen f-muotoon on häviötöntä.

  • d – 64-bittinen liukuluku (kaksinkertainen tarkkuus; noin viisitoista desimaalinumeroa). 32-bittisen MicroPython-float-arvon pakkaaminen d-muotoon levittää sen kahdeksaan tavuun mutta ei lisää tarkkuutta.

  • s – kiinteämittainen tavumerkkijono, jonka edellä on lukumäärä (8s kahdeksantavuiselle kentälle).

2.29.2. Tavujärjestys

Monitavuinen kokonaisluku voidaan tallentaa muistiin kahdella tavalla. Luku 0x12345678 32-bittisessä kentässä asettuu näin:

  • Little-endian – vähiten merkitsevä tavu ensin: 78 56 34 12.

  • Big-endian – eniten merkitsevä tavu ensin: 12 34 56 78.

Molemmat koodaavat saman arvon; ne ovat eri mieltä vain siitä, kumpi kentän pää on matala tavu. Yhden järjestelmän kirjoittama tiedosto on sekaisin, kun toinen lukee sen, jos tavujärjestys ei täsmää.

Muotoilumerkkijonon ensimmäinen merkki valitsee järjestyksen:

  • < – little-endian. Yleinen x86- ja ARM-arkkitehtuureissa.

  • > – big-endian. Yleinen verkkoprotokollissa.

  • ! – verkkojärjestys, vastaa merkkiä >.

Ilman ensimmäistä merkkiä käytetään natiivia tavujärjestystä ja natiivia kohdistusta; <- tai >-merkin nimenomainen asettaminen poistaa tuon monitulkintaisuuden ja on yleensä juuri se, mitä haluat lukiessasi tiedostoa tai keskustellessasi toisen koneen kanssa.

Muista

OpenMV Cam on little-endian – sama kuin sen isäntä-PC. Käytä <-merkkiä muotoilumerkkijonoissa kameran paikallisille tiedostoille ja binääridatalle, joka kulkee pöytäkoneelle tai sieltä pois. Käytä >-merkkiä (tai !) verkkoprotokollissa ja kaikissa muodoissa, joiden määritys edellyttää big-endiania.

Kuusi tavua peräkkäin riviin asetettuna, joista kaksi ensimmäistä tavua on ryhmitelty "H"-kentäksi (16-bittinen etumerkitön) ja seuraavat neljä "I"-kentäksi (32-bittinen etumerkitön), kukin merkittynä little-endian-tavujärjestyksellä.

"<HI" pakkaa 16-bittisen arvon ja sen perään 32-bittisen arvon kuudeksi little-endian-tavuksi.

2.29.3. Pakkaaminen

import struct

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

Tuloste:

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

<HI-muoto tuottaa kuusi tavua: kaksi H-kentälle ja neljä I-kentälle, kaikki little-endian-järjestyksessä. Anna täsmälleen se määrä arvoja, jota muoto odottaa – ristiriita nostaa struct.error-poikkeuksen.

2.29.4. Purkaminen

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

Tuloste:

320 1000000

struct.unpack() palauttaa aina monikon, vaikka muoto kuvaisi yksittäisen kentän. Pura se samalla rivillä luettavuuden vuoksi.

2.29.5. Kiinteämittaiset tavumerkkijonot

s-koodi lukee tai kirjoittaa tavujoukon sellaisenaan. Lukumäärä tulee s-merkin eteen4s tarkoittaa ”neljä tavua käsiteltynä yhtenä tavumerkkijonona”. Tämä on tavanomainen tapa upottaa taikaluku, kiinteäkokoinen tunniste tai täytetty nimikenttä tietueeseen:

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

Tuloste:

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

Neljä ensimmäistä tavua ovat kirjaimellinen taikaluku b"OMV0"; seuraavat kaksi ovat H-kenttä (320); viimeiset neljä ovat I-kenttä (1000000). Purkaminen palauttaa tavut takaisin bytes-objektina:

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

Tuloste:

b'OMV0' 320 1000000

Jos lähdearvo on lyhyempi kuin ilmoitettu lukumäärä, tulos täytetään oikealta \x00-tavuilla; jos pidempi, ylimääräiset tavut pudotetaan hiljaisesti:

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

Lukumäärä on tavupituus, ei merkkien lukumäärä – s käsittelee raakatavuja, joten monitavuisia merkkejä sisältävä UTF-8-merkkijono pitää ensin .encode()-koodata ja laskea tavuina.

2.29.6. Koon laskenta ja osittaiset luvut

struct.calcsize() palauttaa tavujen määrän, jonka muotoilumerkkijono kuluttaa:

struct.calcsize("<HI")     # 6

Kun luet tietueiden virtaa tiedostosta, lue täsmälleen tuo määrä tavuja kutakin tietuetta kohti:

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)

Tiedoston lopussa tapahtuva vajaa luku tuottaa record_size-arvoa pienemmän joukon – käsittele se virran lopun ehtona sen sijaan, että yrittäisit purkaa osittaisen tietueen.