2.29. Struct ve ikili veri

struct modülü Python değerlerini sabit bir ikili düzene paketler ve baytları yeniden Python değerlerine açar. İkili bir dosya biçimiyle, bir ağ protokolüyle veya sabit boyutlu kayıtlar alıp veren bir cihazla çalışırken ona başvurun.

Çoğu durumu iki fonksiyon kapsar:

  • struct.pack() – Python değerlerini ve bir biçim dizesini alır, tam olarak o düzene sahip bir bytes nesnesi döndürür.

  • struct.unpack() – bir biçim dizesini ve bir bytes nesnesini alır, Python değerlerinden oluşan bir demet döndürür.

2.29.1. Biçim dizeleri

Bir biçim dizesi, kayıttaki her alan için bir kod listeler. Kodlar her alanın hem boyutunu hem de yorumlanışını tanımlar.

Python’un int türünün sabit bir boyutu yoktur – atadığınız değere uyacak şekilde büyür. İkili biçimlerin ise sabit boyutları vardır: her tamsayı alanı üzerinde anlaşılan sayıda bayt kullanır. struct, sınırsız Python int’leri ile bu sabit boyutlu gösterimler arasında dönüşüm yapar.

Bir tamsayının genişliği, kullandığı bit sayısıdır. Bir bayt sekiz bittir. Küçük harfli kod işaretli (signed) varyanttır; büyük harfli kod ise işaretsiz (unsigned) olandır (yalnızca negatif olmayan değerler):

  • b / B8 bit (bir bayt). İşaretli -128..127, işaretsiz 0..255.

  • h / H16 bit (iki bayt). İşaretli -32768..32767, işaretsiz 0..65535.

  • i / I32 bit (dört bayt). İşaretli yaklaşık ±iki milyar, işaretsiz dört milyar.

  • q / Q64 bit (sekiz bayt). Günlük kullanım için fiilen sınırsız.

Beklediğiniz aralığı rahatça kapsayan bir genişlik seçin. Bildirilen aralığın dışında bir değeri paketlemek, derlemeye bağlı olarak ya sessizce sarmalanır ya da struct.error hatası verir.

Geriye kalan yaygın kodlar kayan noktalı sayılar ve bayt dizeleri içindir:

  • f – 32 bit kayan noktalı sayı (tek hassasiyet; yaklaşık yedi ondalık basamak). MicroPython’da Python’un normal float türü zaten bu boyuttadır, dolayısıyla birini f içine paketlemek kayıpsızdır.

  • d – 64 bit kayan noktalı sayı (çift hassasiyet; yaklaşık on beş ondalık basamak). 32 bitlik bir MicroPython float değerini d içine paketlemek onu sekiz bayta genişletir ancak hassasiyet eklemez.

  • s – sabit uzunluklu bayt dizesi, önünde bir sayım ile (sekiz baytlık bir alan için 8s).

2.29.2. Bayt sırası

Çok baytlı bir tamsayı bellekte iki şekilde saklanabilir. 32 bitlik bir alandaki 0x12345678 sayısı şöyle yerleştirilir:

  • Little-endian – önce en az anlamlı bayt: 78 56 34 12.

  • Big-endian – önce en anlamlı bayt: 12 34 56 78.

İkisi de aynı değeri kodlar; yalnızca alanın hangi ucunun düşük bayt olduğu konusunda anlaşamazlar. Bir sistem tarafından yazılan bir dosya, bayt sırası eşleşmezse diğeri tarafından okunduğunda bozuk görünür.

Biçim dizesinin baştaki karakteri sırayı belirler:

  • < – little-endian. x86 ve ARM’de yaygındır.

  • > – big-endian. Ağ protokollerinde yaygındır.

  • ! – ağ sırası, > ile eşdeğerdir.

Baştaki bir karakter olmadan, yerel bayt sırası ve yerel hizalama kullanılır; < veya > ifadesini açıkça belirtmek bu belirsizliği ortadan kaldırır ve genellikle bir dosya okurken ya da başka bir makineyle konuşurken istediğiniz şeydir.

Not

OpenMV Cam little-endian‘dir – ana bilgisayar PC’siyle aynı. Kamera-yerel dosyalar ve bir masaüstüne gidip gelen ikili veriler için biçim dizelerinde < kullanın. Ağ protokolleri ve spesifikasyonu big-endian gerektiren herhangi bir biçim için > (veya !) kullanın.

Bir satıra dizilmiş altı bayt; ilk iki bayt bir "H" alanı (16 bit işaretsiz), sonraki dört bayt ise bir "I" alanı (32 bit işaretsiz) olarak gruplanmış ve her biri little-endian bayt sırasıyla etiketlenmiştir.

"<HI" bir 16 bitlik değeri ve ardından bir 32 bitlik değeri altı little-endian bayta paketler.

2.29.3. Paketleme

import struct

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

Çıktı:

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

<HI biçimi altı bayt üretir: H alanı için iki ve I alanı için dört, hepsi little-endian. Biçimin beklediği değer sayısının tam olarak aynısını geçirin – bir uyumsuzluk struct.error hatası verir.

2.29.4. Paketi açma

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

Çıktı:

320 1000000

struct.unpack(), biçim tek bir alanı tanımlasa bile her zaman bir demet döndürür. Okunabilirlik için onu aynı satırda açın.

2.29.5. Sabit uzunluklu bayt dizeleri

s kodu bir bayt yığınını olduğu gibi okur veya yazar. Sayım s ifadesinin önüne gelir – 4s “tek bir bayt dizesi olarak ele alınan dört bayt” anlamına gelir. Bu, bir kayda bir sihirli değer, sabit boyutlu bir etiket veya dolgulu bir ad alanı gömmenin olağan yoludur:

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

Çıktı:

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

İlk dört bayt, gerçek sihirli değer olan b"OMV0"‘dır; sonraki ikisi H alanıdır (320); son dördü ise I alanıdır (1000000). Paketi açmak baytları yeniden bir bytes nesnesi olarak döndürür:

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

Çıktı:

b'OMV0' 320 1000000

Kaynak değer bildirilen sayımdan kısaysa, sonuç sağdan \x00 ile doldurulur; daha uzunsa, fazla baytlar sessizce atılır:

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

Sayım bir bayt uzunluğudur, karakter sayımı değildir – s ham baytlarla ilgilenir, bu nedenle çok baytlı karakterler içeren bir UTF-8 dizesinin önce .encode() ile kodlanması ve bayt cinsinden sayılması gerekir.

2.29.6. Boyutlandırma ve kısmi okumalar

struct.calcsize(), bir biçim dizesinin tükettiği bayt sayısını döndürür:

struct.calcsize("<HI")     # 6

Bir dosyadan kayıt akışı okurken, kayıt başına tam olarak o kadar bayt okuyun:

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)

Dosyanın sonundaki kısa bir okuma, record_size değerinden daha küçük bir yığın üretir – bunu kısmi bir kaydın paketini açmaya çalışmak yerine akış sonu durumu olarak ele alın.