2.29. Struct und Binärdaten¶
Das Modul struct packt Python-Werte in ein festes Binärlayout und entpackt Bytes wieder in Python-Werte. Greifen Sie darauf zurück, wenn Sie mit einem binären Dateiformat, einem Netzwerkprotokoll oder einem Gerät arbeiten, das Datensätze fester Größe austauscht.
Zwei Funktionen decken die meisten Fälle ab:
struct.pack()– nimmt Python-Werte und einen Formatstring entgegen und gibt einbytes-Objekt mit dem exakten Layout zurück.struct.unpack()– nimmt einen Formatstring und einbytes-Objekt entgegen und gibt ein Tupel von Python-Werten zurück.
2.29.1. Formatstrings¶
Ein Formatstring listet einen Code pro Feld im Datensatz auf. Die Codes beschreiben sowohl die Größe als auch die Interpretation jedes Feldes.
Pythons int hat keine feste Größe – er wächst, um jeden zugewiesenen Wert aufzunehmen. Binärformate haben dagegen feste Größen: Jedes Ganzzahlfeld verwendet eine vereinbarte Anzahl von Bytes. struct konvertiert zwischen unbegrenzten Python-Ganzzahlen und diesen Darstellungen fester Größe.
Die Breite einer Ganzzahl ist die Anzahl der Bits, die sie verwendet. Ein Byte besteht aus acht Bits. Der Kleinbuchstaben-Code ist die vorzeichenbehaftete Variante; der Großbuchstaben-Code ist die vorzeichenlose (nur nicht-negative Werte):
b/B– 8 Bit (ein Byte).-128..127vorzeichenbehaftet,0..255vorzeichenlos.h/H– 16 Bit (zwei Bytes).-32768..32767vorzeichenbehaftet,0..65535vorzeichenlos.i/I– 32 Bit (vier Bytes). Etwa ±zwei Milliarden vorzeichenbehaftet, vier Milliarden vorzeichenlos.q/Q– 64 Bit (acht Bytes). Für den alltäglichen Gebrauch praktisch unbegrenzt.
Wählen Sie eine Breite, die den erwarteten Bereich bequem abdeckt. Das Packen eines Wertes außerhalb des deklarierten Bereichs führt je nach Build entweder zu einem stillen Überlauf oder löst struct.error aus.
Die übrigen gängigen Codes sind für Gleitkommazahlen und Byte-Strings:
f– 32-Bit-Gleitkommazahl (einfache Genauigkeit; etwa sieben Dezimalstellen). Pythons reguläresfloatauf MicroPython hat bereits diese Größe, sodass das Packen infverlustfrei ist.d– 64-Bit-Gleitkommazahl (doppelte Genauigkeit; etwa fünfzehn Dezimalstellen). Das Packen eines 32-Bit-MicroPython-floatinderweitert ihn auf acht Bytes, fügt aber keine Genauigkeit hinzu.s– Byte-String fester Länge, dem eine Anzahl vorangestellt ist (8sfür ein Acht-Byte-Feld).
2.29.2. Bytereihenfolge¶
Eine Mehrbyte-Ganzzahl kann auf zwei Arten im Speicher abgelegt werden. Die Zahl 0x12345678 wird in einem 32-Bit-Feld wie folgt angeordnet:
Little-Endian – niederwertigstes Byte zuerst:
78 56 34 12.Big-Endian – höchstwertiges Byte zuerst:
12 34 56 78.
Beide kodieren denselben Wert; sie unterscheiden sich nur darin, an welchem Ende des Feldes das niederwertige Byte steht. Eine von einem System geschriebene Datei wird beim Lesen durch ein anderes verstümmelt, wenn die Bytereihenfolge nicht übereinstimmt.
Das führende Zeichen des Formatstrings legt die Reihenfolge fest:
<– Little-Endian. Üblich auf x86 und ARM.>– Big-Endian. Üblich in Netzwerkprotokollen.!– Netzwerkreihenfolge, gleichbedeutend mit>.
Ohne führendes Zeichen werden die native Bytereihenfolge und die native Ausrichtung verwendet; das explizite Setzen von < oder > beseitigt diese Mehrdeutigkeit und ist normalerweise das, was Sie beim Lesen einer Datei oder bei der Kommunikation mit einem anderen Rechner wünschen.
Bemerkung
Die OpenMV Cam ist Little-Endian – genau wie ihr Host-PC. Verwenden Sie < in Formatstrings für kameralokale Dateien und für Binärdaten, die zu oder von einem Desktop übertragen werden. Verwenden Sie > (oder !) für Netzwerkprotokolle und für jedes Format, dessen Spezifikation Big-Endian vorschreibt.
"<HI" packt einen 16-Bit-Wert gefolgt von einem 32-Bit-Wert in sechs Little-Endian-Bytes.¶
2.29.3. Packen¶
import struct
blob = struct.pack("<HI", 320, 1000000)
print(blob, len(blob))
Ausgabe:
b'@\x01@B\x0f\x00' 6
Das Format <HI erzeugt sechs Bytes: zwei für das H-Feld und vier für das I-Feld, alle in Little-Endian. Übergeben Sie genau die Anzahl von Werten, die das Format erwartet – eine Abweichung löst struct.error aus.
2.29.4. Entpacken¶
width, count = struct.unpack("<HI", blob)
print(width, count)
Ausgabe:
320 1000000
struct.unpack() gibt immer ein Tupel zurück, selbst wenn das Format ein einzelnes Feld beschreibt. Entpacken Sie es zur besseren Lesbarkeit in derselben Zeile.
2.29.5. Byte-Strings fester Länge¶
Der s-Code liest oder schreibt einen Block von Bytes unverändert. Die Anzahl steht vor dem s – 4s bedeutet „vier Bytes, die als ein einzelner Byte-String behandelt werden“. Dies ist die übliche Methode, um einen Magic-Wert, ein Tag fester Größe oder ein aufgefülltes Namensfeld in einen Datensatz einzubetten:
header = struct.pack("<4sHI", b"OMV0", 320, 1000000)
print(header)
Ausgabe:
b'OMV0@\x01@B\x0f\x00'
Die ersten vier Bytes sind der literale Magic-Wert b"OMV0"; die nächsten zwei sind das H-Feld (320); die letzten vier sind das I-Feld (1000000). Das Entpacken gibt die Bytes wieder als bytes-Objekt zurück:
magic, width, count = struct.unpack("<4sHI", header)
print(magic, width, count)
Ausgabe:
b'OMV0' 320 1000000
Ist der Quellwert kürzer als die deklarierte Anzahl, wird das Ergebnis rechts mit \x00 aufgefüllt; ist er länger, werden die überschüssigen Bytes stillschweigend verworfen:
struct.pack("4s", b"hi") # b'hi\x00\x00'
struct.pack("4s", b"toolong") # b'tool'
Die Anzahl ist eine Byte-Länge, keine Zeichenanzahl – s arbeitet mit rohen Bytes, sodass ein UTF-8-String mit Mehrbyte-Zeichen zuerst mit .encode() kodiert und in Bytes gezählt werden muss.
2.29.6. Größenbestimmung und Teillesevorgänge¶
struct.calcsize() gibt die Anzahl der Bytes zurück, die ein Formatstring verbraucht:
struct.calcsize("<HI") # 6
Beim Lesen eines Stroms von Datensätzen aus einer Datei lesen Sie genau diese Anzahl von Bytes pro Datensatz:
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)
Ein verkürzter Lesevorgang am Ende der Datei erzeugt einen Block, der kleiner als record_size ist – behandeln Sie dies als Stream-Ende-Bedingung, anstatt zu versuchen, einen unvollständigen Datensatz zu entpacken.