2.29. Struct och binärdata¶
Modulen struct packar Python-värden till en fast binär layout och packar upp byte tillbaka till Python-värden. Använd den när du arbetar med ett binärt filformat, ett nätverksprotokoll eller en enhet som utbyter poster av fast storlek.
Två funktioner täcker de flesta fall:
struct.pack()– tar emot Python-värden och en formatsträng och returnerar ettbytes-objekt med exakt den layouten.struct.unpack()– tar emot en formatsträng och ettbytes-objekt och returnerar en tupel med Python-värden.
2.29.1. Formatsträngar¶
En formatsträng listar en kod per fält i posten. Koderna beskriver både storleken och tolkningen av varje fält.
Pythons int har ingen fast storlek – den växer för att rymma vilket värde du än tilldelar. Binära format har däremot fasta storlekar: varje heltalsfält använder ett överenskommet antal byte. struct konverterar mellan obegränsade Python-heltal och dessa representationer av fast storlek.
Ett heltals bredd är antalet bitar det använder. En byte är åtta bitar. Den gemena koden är den teckenförsedda varianten; den versala koden är den teckenlösa (endast icke-negativa värden):
b/B– 8-bitars (en byte).-128..127med tecken,0..255utan tecken.h/H– 16-bitars (två byte).-32768..32767med tecken,0..65535utan tecken.i/I– 32-bitars (fyra byte). Ungefär ±två miljarder med tecken, fyra miljarder utan tecken.q/Q– 64-bitars (åtta byte). I praktiken obegränsad för vardagligt bruk.
Välj en bredd som med god marginal täcker det intervall du förväntar dig. Att packa ett värde utanför det deklarerade intervallet leder antingen till att det tyst slår runt eller till att struct.error höjs, beroende på bygget.
De övriga vanliga koderna är för flyttal och bytesträngar:
f– 32-bitars flyttal (enkel precision; ungefär sju decimala siffror). Pythons vanligafloatpå MicroPython har redan denna storlek, så att packa ett sådant tillfär förlustfritt.d– 64-bitars flyttal (dubbel precision; ungefär femton decimala siffror). Att packa ett 32-bitars MicroPython-floattilldbreddar det till åtta byte men tillför ingen precision.s– bytesträng med fast längd, föregången av ett antal (8sför ett åtta byte stort fält).
2.29.2. Byteordning¶
Ett heltal med flera byte kan lagras i minnet på två sätt. Talet 0x12345678 i ett 32-bitars fält läggs ut så här:
Little-endian – minst signifikanta byte först:
78 56 34 12.Big-endian – mest signifikanta byte först:
12 34 56 78.
Båda kodar samma värde; de är bara oeniga om vilken ände av fältet som är den låga byten. En fil som skrivits av ett system blir förvrängd när den läses av ett annat om byteordningen inte stämmer.
Det inledande tecknet i formatsträngen väljer ordningen:
<– little-endian. Vanlig på x86 och ARM.>– big-endian. Vanlig i nätverksprotokoll.!– nätverksordning, motsvarar>.
Utan ett inledande tecken används systemets egna byteordning och egna justering; att uttryckligen sätta < eller > tar bort den tvetydigheten och är oftast vad du vill ha när du läser en fil eller kommunicerar med en annan maskin.
Anteckning
OpenMV Cam är little-endian – samma som dess värd-PC. Använd < i formatsträngar för kameralokala filer och för binärdata som färdas till eller från en dator. Använd > (eller !) för nätverksprotokoll och för alla format vars specifikation kräver big-endian.
"<HI" packar ett 16-bitars värde följt av ett 32-bitars värde till sex little-endian-byte.¶
2.29.3. Packning¶
import struct
blob = struct.pack("<HI", 320, 1000000)
print(blob, len(blob))
Utdata:
b'@\x01@B\x0f\x00' 6
Formatet <HI producerar sex byte: två för fältet H och fyra för fältet I, alla little-endian. Skicka in exakt det antal värden som formatet förväntar sig – en avvikelse höjer struct.error.
2.29.4. Uppackning¶
width, count = struct.unpack("<HI", blob)
print(width, count)
Utdata:
320 1000000
struct.unpack() returnerar alltid en tupel, även när formatet beskriver ett enda fält. Packa upp den på samma rad för läsbarhetens skull.
2.29.5. Bytesträngar med fast längd¶
Koden s läser eller skriver en bunt byte ordagrant. Antalet anges före s – 4s betyder ”fyra byte behandlade som en enda bytesträng”. Detta är det vanliga sättet att bädda in ett magiskt värde, en tagg av fast storlek eller ett utfyllt namnfält i en post:
header = struct.pack("<4sHI", b"OMV0", 320, 1000000)
print(header)
Utdata:
b'OMV0@\x01@B\x0f\x00'
De första fyra bytena är det litterala magiska värdet b"OMV0"; de nästa två är fältet H (320); de sista fyra är fältet I (1000000). Uppackning returnerar bytena tillbaka som ett bytes-objekt:
magic, width, count = struct.unpack("<4sHI", header)
print(magic, width, count)
Utdata:
b'OMV0' 320 1000000
Om källvärdet är kortare än det deklarerade antalet fylls resultatet ut till höger med \x00; om det är längre kasseras de överskjutande bytena tyst:
struct.pack("4s", b"hi") # b'hi\x00\x00'
struct.pack("4s", b"toolong") # b'tool'
Antalet är en bytelängd, inte ett teckenantal – s hanterar råa byte, så en UTF-8-sträng med tecken på flera byte måste först köras genom .encode() och räknas i byte.
2.29.6. Storleksberäkning och partiella läsningar¶
struct.calcsize() returnerar antalet byte som en formatsträng förbrukar:
struct.calcsize("<HI") # 6
När du läser en ström av poster från en fil, läs exakt så många byte per post:
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)
En kort läsning i slutet av filen ger en bunt som är mindre än record_size – behandla det som slutet-på-ström-villkoret snarare än att försöka packa upp en partiell post.