2.29. Struct dan data biner

Modul struct mengemas nilai Python ke dalam tata letak biner tetap dan mengurai byte kembali menjadi nilai Python. Gunakan modul ini saat bekerja dengan format file biner, protokol jaringan, atau perangkat yang menukar rekaman berukuran tetap.

Dua fungsi mencakup sebagian besar kasus:

  • struct.pack() -- mengambil nilai Python dan sebuah string format, mengembalikan objek bytes dengan tata letak yang tepat.

  • struct.unpack() -- mengambil string format dan objek bytes, mengembalikan tuple nilai Python.

2.29.1. String format

Sebuah string format mencantumkan satu kode per bidang dalam rekaman. Kode-kode tersebut mendeskripsikan ukuran dan interpretasi dari setiap bidang.

int Python tidak memiliki ukuran tetap -- ia tumbuh sesuai nilai yang Anda tetapkan. Format biner memiliki ukuran tetap: setiap bidang bilangan bulat menggunakan jumlah byte yang sudah disepakati. struct mengonversi antara int Python yang tak terbatas dengan representasi berukuran tetap ini.

Lebar bilangan bulat adalah jumlah bit yang digunakannya. Satu byte terdiri dari delapan bit. Kode huruf kecil adalah varian bertanda (signed); kode huruf besar adalah yang tidak bertanda (unsigned, hanya nilai non-negatif):

  • b / B -- 8-bit (satu byte). -128..127 bertanda, 0..255 tidak bertanda.

  • h / H -- 16-bit (dua byte). -32768..32767 bertanda, 0..65535 tidak bertanda.

  • i / I -- 32-bit (empat byte). Sekitar ±dua miliar bertanda, empat miliar tidak bertanda.

  • q / Q -- 64-bit (delapan byte). Secara efektif tidak terbatas untuk penggunaan sehari-hari.

Pilih lebar yang nyaman mencakup rentang yang Anda harapkan. Mengemas nilai di luar rentang yang dideklarasikan bisa secara diam-diam melakukan wrapping atau memunculkan struct.error, tergantung pada build-nya.

Kode umum lainnya adalah untuk float dan string byte:

  • f -- float 32-bit (presisi tunggal; sekitar tujuh digit desimal). float biasa Python di MicroPython sudah berukuran ini, sehingga mengemasnya ke dalam f tidak mengalami kehilangan presisi.

  • d -- float 64-bit (presisi ganda; sekitar lima belas digit desimal). Mengemas float MicroPython 32-bit ke dalam d memperlebarnya menjadi delapan byte tetapi tidak menambah presisi.

  • s -- string byte dengan panjang tetap, didahului oleh jumlah byte (8s untuk bidang delapan byte).

2.29.2. Urutan byte

Bilangan bulat multi-byte dapat disimpan di memori dengan dua cara. Angka 0x12345678 dalam bidang 32-bit ditata seperti ini:

  • Little-endian -- byte paling tidak signifikan terlebih dahulu: 78 56 34 12.

  • Big-endian -- byte paling signifikan terlebih dahulu: 12 34 56 78.

Keduanya mengodekan nilai yang sama; perbedaannya hanya pada ujung mana dari bidang yang merupakan byte rendah. File yang ditulis oleh satu sistem akan berantakan saat dibaca oleh sistem lain jika urutan byte tidak cocok.

Karakter pertama dari string format menentukan urutan:

  • < -- little-endian. Umum pada x86 dan ARM.

  • > -- big-endian. Umum dalam protokol jaringan.

  • ! -- urutan jaringan, setara dengan >.

Tanpa karakter awal, urutan byte dan penyelarasan (alignment) native yang digunakan; menetapkan < atau > secara eksplisit menghilangkan ambiguitas tersebut dan biasanya itulah yang Anda inginkan saat membaca file atau berkomunikasi dengan mesin lain.

Catatan

OpenMV Cam bersifat little-endian -- sama seperti PC host-nya. Gunakan < dalam string format untuk file lokal kamera dan untuk data biner yang dikirim ke atau dari desktop. Gunakan > (atau !) untuk protokol jaringan dan untuk format apa pun yang spesifikasinya menggunakan big-endian.

Six bytes laid out in a row, with the first two bytes grouped as an "H" field (16-bit unsigned) and the next four as an "I" field (32-bit unsigned), each labelled with their little-endian byte order.

"<HI" mengemas nilai 16-bit diikuti nilai 32-bit ke dalam enam byte little-endian.

2.29.3. Pengemasan

import struct

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

Output:

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

Format <HI menghasilkan enam byte: dua untuk bidang H dan empat untuk bidang I, semuanya little-endian. Berikan tepat jumlah nilai yang diharapkan format -- ketidakcocokan akan memunculkan struct.error.

2.29.4. Pembongkaran

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

Output:

320 1000000

struct.unpack() selalu mengembalikan tuple, bahkan ketika format hanya mendeskripsikan satu bidang. Bongkar hasilnya pada baris yang sama untuk keterbacaan.

2.29.5. String byte dengan panjang tetap

Kode s membaca atau menulis sekumpulan byte secara verbatim. Jumlah byte ditulis sebelum s -- 4s berarti "empat byte yang diperlakukan sebagai satu string byte". Ini adalah cara umum untuk menyematkan nilai magic, tag berukuran tetap, atau bidang nama berisi padding dalam sebuah rekaman:

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

Output:

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

Empat byte pertama adalah magic literal b"OMV0"; dua berikutnya adalah bidang H (320); empat terakhir adalah bidang I (1000000). Pembongkaran mengembalikan byte tersebut sebagai objek bytes:

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

Output:

b'OMV0' 320 1000000

Jika nilai sumber lebih pendek dari jumlah yang dideklarasikan, hasilnya diisi di sisi kanan dengan \x00; jika lebih panjang, byte yang berlebih akan dihapus secara diam-diam:

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

Jumlah tersebut adalah panjang dalam byte, bukan jumlah karakter -- s bekerja dengan byte mentah, sehingga string UTF-8 dengan karakter multi-byte perlu di-.encode() dan dihitung dalam byte terlebih dahulu.

2.29.6. Menghitung ukuran dan pembacaan parsial

struct.calcsize() mengembalikan jumlah byte yang dikonsumsi oleh sebuah string format:

struct.calcsize("<HI")     # 6

Saat membaca aliran rekaman dari sebuah file, baca tepat sebanyak itu byte per rekaman:

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)

Pembacaan singkat di akhir file menghasilkan potongan yang lebih kecil dari record_size -- perlakukan itu sebagai kondisi akhir aliran daripada mencoba membongkar rekaman yang tidak lengkap.