2.29. Struct และข้อมูลไบนารี

โมดูล struct ใช้บรรจุค่า Python ลงในรูปแบบไบนารีคงที่ และแกะไบต์กลับเป็นค่า Python อีกครั้ง ใช้งานเมื่อต้องการทำงานกับรูปแบบไฟล์ไบนารี โปรโตคอลเครือข่าย หรืออุปกรณ์ที่รับส่งเรคคอร์ดขนาดคงที่

ฟังก์ชันสองตัวครอบคลุมกรณีส่วนใหญ่:

  • struct.pack() -- รับค่า Python และ สตริงรูปแบบ แล้วคืนค่าอ็อบเจกต์ bytes ที่มีเลย์เอาต์ตรงตามที่กำหนด

  • struct.unpack() -- รับสตริงรูปแบบและอ็อบเจกต์ bytes แล้วคืนค่าเป็น tuple ของค่า Python

2.29.1. สตริงรูปแบบ

สตริงรูปแบบระบุ โค้ด หนึ่งตัวต่อหนึ่งฟิลด์ในเรคคอร์ด โค้ดเหล่านี้อธิบายทั้งขนาดและการตีความของแต่ละฟิลด์

int ของ Python ไม่มีขนาดคงที่ -- ขยายตัวตามค่าที่กำหนด แต่รูปแบบไบนารี มี ขนาดคงที่: ทุกฟิลด์จำนวนเต็มใช้จำนวนไบต์ที่ตกลงกันไว้ struct แปลงระหว่างจำนวนเต็ม Python ที่ไม่มีขอบเขตและการแทนค่าขนาดคงที่เหล่านี้

ความกว้าง ของจำนวนเต็มคือจำนวนบิตที่ใช้ หนึ่งไบต์เท่ากับแปดบิต โค้ดตัวพิมพ์เล็กคือรูปแบบ signed ส่วนตัวพิมพ์ใหญ่คือ unsigned (เฉพาะค่าที่ไม่ติดลบ):

  • b / B -- 8 บิต (หนึ่งไบต์) -128..127 แบบ signed, 0..255 แบบ unsigned

  • h / H -- 16 บิต (สองไบต์) -32768..32767 แบบ signed, 0..65535 แบบ unsigned

  • i / I -- 32 บิต (สี่ไบต์) ประมาณ ±สองพันล้านแบบ signed, สี่พันล้านแบบ unsigned

  • q / Q -- 64 บิต (แปดไบต์) ขนาดใหญ่เกินพอสำหรับการใช้งานทั่วไป

เลือกความกว้างที่ครอบคลุมช่วงค่าที่คาดไว้อย่างสบาย การบรรจุค่าที่อยู่นอกช่วงที่กำหนดอาจวนรอบแบบเงียบหรือยก struct.error ขึ้น ขึ้นอยู่กับการคอมไพล์

โค้ดที่เหลือที่ใช้บ่อยสำหรับตัวเลขทศนิยมและสตริงไบต์:

  • f -- ทศนิยม 32 บิต (ความแม่นยำเดี่ยว; ประมาณเจ็ดหลักทศนิยม) float ปกติของ Python บน MicroPython มีขนาดนี้อยู่แล้ว การบรรจุลงใน f จึงไม่สูญเสียข้อมูล

  • d -- ทศนิยม 64 บิต (ความแม่นยำคู่; ประมาณสิบห้าหลักทศนิยม) การบรรจุ float 32 บิตของ MicroPython ลงใน d จะขยายเป็นแปดไบต์แต่ไม่เพิ่มความแม่นยำ

  • s -- สตริงไบต์ความยาวคงที่ นำหน้าด้วยจำนวน (8s หมายถึงฟิลด์แปดไบต์)

2.29.2. ลำดับไบต์

จำนวนเต็มหลายไบต์สามารถเก็บในหน่วยความจำได้สองแบบ ตัวเลข 0x12345678 ในฟิลด์ 32 บิตมีเลย์เอาต์ดังนี้:

  • Little-endian -- ไบต์ที่มีนัยสำคัญน้อยที่สุดอยู่ก่อน: 78 56 34 12

  • Big-endian -- ไบต์ที่มีนัยสำคัญมากที่สุดอยู่ก่อน: 12 34 56 78

ทั้งสองแบบเข้ารหัสค่าเดียวกัน แต่ไม่ตกลงกันว่าปลายใดของฟิลด์คือไบต์ต่ำ ไฟล์ที่เขียนโดยระบบหนึ่งจะอ่านผิดเพี้ยนเมื่ออ่านโดยอีกระบบหากลำดับไบต์ไม่ตรงกัน

อักขระนำหน้าของสตริงรูปแบบกำหนดลำดับ:

  • < -- little-endian พบบ่อยบน x86 และ ARM

  • > -- big-endian พบบ่อยในโปรโตคอลเครือข่าย

  • ! -- ลำดับเครือข่าย เทียบเท่ากับ >

หากไม่มีอักขระนำหน้า จะใช้ลำดับไบต์และการจัดตำแหน่งแบบ native การระบุ < หรือ > อย่างชัดเจนจะขจัดความกำกวมนั้น และมักเป็นสิ่งที่ต้องการเมื่ออ่านไฟล์หรือสื่อสารกับเครื่องอื่น

Note

OpenMV Cam เป็นแบบ little-endian -- เหมือนกับ PC host ใช้ < ในสตริงรูปแบบสำหรับไฟล์ในกล้องและข้อมูลไบนารีที่รับส่งกับเดสก์ท็อป ใช้ > (หรือ !) สำหรับโปรโตคอลเครือข่ายและรูปแบบใดก็ตามที่ข้อกำหนดกำหนดให้ใช้ 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" บรรจุค่า 16 บิตตามด้วยค่า 32 บิตลงในหกไบต์แบบ little-endian

2.29.3. การบรรจุข้อมูล

import struct

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

ผลลัพธ์:

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

รูปแบบ <HI สร้างหกไบต์: สองไบต์สำหรับฟิลด์ H และสี่ไบต์สำหรับฟิลด์ I ทั้งหมดแบบ little-endian ต้องส่งจำนวนค่าที่ตรงกับที่รูปแบบต้องการ -- ถ้าไม่ตรงจะยก struct.error

2.29.4. การแกะข้อมูล

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

ผลลัพธ์:

320 1000000

struct.unpack() คืนค่าเป็น tuple เสมอ แม้ว่ารูปแบบจะอธิบายฟิลด์เดียว แกะ tuple ในบรรทัดเดียวเพื่อความสะดวกในการอ่าน

2.29.5. สตริงไบต์ความยาวคงที่

โค้ด s อ่านหรือเขียนกลุ่มไบต์ตามตัวอักษร จำนวนต้องอยู่ ก่อน s -- 4s หมายถึง "สี่ไบต์ที่ถือเป็นสตริงไบต์เดียว" นี่คือวิธีปกติในการฝัง magic value, แท็กขนาดคงที่, หรือฟิลด์ชื่อที่เติม padding ลงในเรคคอร์ด:

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

ผลลัพธ์:

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

สี่ไบต์แรกคือ magic b"OMV0" ตามตัวอักษร; สองไบต์ถัดไปคือฟิลด์ H (320); สี่ไบต์สุดท้ายคือฟิลด์ I (1000000) การแกะข้อมูลจะคืนไบต์กลับเป็นอ็อบเจกต์ bytes:

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

ผลลัพธ์:

b'OMV0' 320 1000000

หากค่าต้นฉบับสั้นกว่าจำนวนที่กำหนด ผลลัพธ์จะเติม \x00 ทางด้านขวา; หากยาวกว่า ไบต์ส่วนเกินจะถูกทิ้งแบบเงียบ:

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

จำนวนคือความยาวไบต์ ไม่ใช่จำนวนอักขระ -- s ทำงานกับไบต์ดิบ ดังนั้นสตริง UTF-8 ที่มีอักขระหลายไบต์จะต้อง .encode() ก่อนและนับเป็นไบต์

2.29.6. การคำนวณขนาดและการอ่านบางส่วน

struct.calcsize() คืนจำนวนไบต์ที่สตริงรูปแบบใช้:

struct.calcsize("<HI")     # 6

เมื่ออ่านสตรีมเรคคอร์ดจากไฟล์ ให้อ่านไบต์นั้นจำนวนที่แน่นอนต่อเรคคอร์ด:

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)

การอ่านที่ได้ข้อมูลไม่ครบที่ปลายไฟล์จะได้กลุ่มข้อมูลที่เล็กกว่า record_size -- ให้ถือว่านั่นคือเงื่อนไขสิ้นสุดสตรีมแทนที่จะพยายามแกะเรคคอร์ดที่ไม่สมบูรณ์