42.2 Interpretieren von Binärdaten – struct 

Wenn eine Datei im Binärmodus gelesen wird, kommt der Dateiinhalt im Programm als Byte-Folge an. Ein Byte besteht aus acht Bit und repräsentiert somit eine Zahl zwischen 0 und 255. Werte, die nicht in diesen Zahlenraum passen, werden durch eine Folge von Bytes dargestellt. In Abschnitt 6.4.3, »Die Schreib-/Leseposition verändern«, haben wir eine Grafik im Bitmap-Dateiformat binär eingelesen, um die Höhe, die Breite und die Farbtiefe des Bildes zu erhalten:
def bytes2int(b):
res = 0
for x in b[::-1]:
res = (res << 8) + x
return res
with open("bild.bmp", "rb") as f:
f.seek(18)
print("Breite:", bytes2int(f.read(4)), "px")
print("Höhe:", bytes2int(f.read(4)), "px")
f.seek(2, 1)
print("Farbtiefe:", bytes2int(f.read(2)), "bpp")
Da Bilder eine Kantenlänge von mehr als 255 Pixeln haben können, werden Höhe und Breite des Bildes im Bitmap-Format durch eine Folge von vier Bytes gespeichert. Zur Umrechnung dieser Byte-Folge in eine Zahl dient die Funktion bytes2int, die die Byte-Folge rückwärts durchläuft, das jeweils aktuelle Zwischenergebnis in seiner Binärdarstellung um acht Bit nach links verschiebt und dann den nächsten Byte-Wert aufaddiert. Auf diese Weise lassen sich vier 8-Bit-Werte zu einem 32-Bit-Wert zusammenfügen.
Pythons Standardbibliothek enthält das Modul struct, das uns das Interpretieren von Byte-Folgen abnimmt. Das oben dargestellte Beispiel lässt sich unter Verwendung des Moduls struct folgendermaßen schreiben:
import struct
with open("bild.bmp", "rb") as f:
f.seek(18)
werte = struct.unpack("iihh", f.read(12))
print("Breite:", werte[0], "px")
print("Höhe:", werte[1], "px")
print("Farbtiefe:", werte[3], "bpp")
Zunächst wird die Leseposition wie gehabt auf das 18. Byte gesetzt. Danach lesen wir die folgenden 12 Byte in einem Rutsch und übergeben sie zusammen mit einer Formatbeschreibung an die Funktion unpack des Moduls struct. Die Funktion unpack zerlegt die Byte-Folge anhand der Formatbeschreibung und fügt die Einzelwerte zusammen. Das von unpack zurückgegebene Tupel enthält die interpretierten Werte.
Tabelle 42.1 listet die wichtigsten Formatangaben zusammen mit der Größe und dem Python-Datentyp auf, in den sie konvertiert werden.
Angabe | Datentyp | Größe | Vorzeichenbehaftet |
---|---|---|---|
c | bytes | 1 Byte* | – |
b | int | 1 Byte | ja |
B | int | 1 Byte | nein |
? | bool | 1 Byte | – |
h | int | 2 Byte | ja |
H | int | 2 Byte | nein |
i | int | 4 Byte | ja |
I | int | 4 Byte | nein |
l | int | 4 Byte | ja |
L | int | 4 Byte | nein |
q | int | 8 Byte | ja |
Q | int | 8 Byte | nein |
e | float | 2 Byte | ja |
f | float | 4 Byte | ja |
d | float | 8 Byte | ja |
s | bytes | – | – |
* Die Formatangabe c steht für ein einzelnes Zeichen, während s für einen String von beliebiger Länge steht. |
Tabelle 42.1 Formatangaben bei unpack
Abgesehen von der Funktion unpack enthält das Modul struct noch weitere Funktionen, die im Folgenden kurz beschrieben werden:
-
pack(fmt, [*values])
Diese Funktion ist das Gegenstück zu unpack: Sie codiert die übergebenen Werte anhand der Formatangaben fmt in eine Byte-Folge.>>> struct.pack("iif", 12, 34, 5.67)
b'\x0c\x00\x00\x00"\x00\x00\x00\xa4p\xb5@' -
pack_into(fmt, buffer, offset, [*values])
Diese Funktion arbeitet wie pack, schreibt die codierten Daten aber an der Stelle offset in die bytearray-Instanz buffer. -
unpack_from(fmt, buffer, [offset])
Diese Funktion arbeitet wie unpack, liest die Daten aber erst ab der Stelle offset aus der Byte-Folge. Mithilfe von unpack_from lässt sich im oben dargestellten Bitmap-Beispiel der seek-Aufruf einsparen. -
calcsize(fmt)
Diese Funktion gibt die Größe der Byte-Folge zurück, die durch einen Aufruf von pack mit der Formatangabe fmt entstehen würde.