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แบบ unsignedh/H-- 16 บิต (สองไบต์)-32768..32767แบบ signed,0..65535แบบ unsignedi/I-- 32 บิต (สี่ไบต์) ประมาณ ±สองพันล้านแบบ signed, สี่พันล้านแบบ unsignedq/Q-- 64 บิต (แปดไบต์) ขนาดใหญ่เกินพอสำหรับการใช้งานทั่วไป
เลือกความกว้างที่ครอบคลุมช่วงค่าที่คาดไว้อย่างสบาย การบรรจุค่าที่อยู่นอกช่วงที่กำหนดอาจวนรอบแบบเงียบหรือยก struct.error ขึ้น ขึ้นอยู่กับการคอมไพล์
โค้ดที่เหลือที่ใช้บ่อยสำหรับตัวเลขทศนิยมและสตริงไบต์:
f-- ทศนิยม 32 บิต (ความแม่นยำเดี่ยว; ประมาณเจ็ดหลักทศนิยม)floatปกติของ Python บน MicroPython มีขนาดนี้อยู่แล้ว การบรรจุลงในfจึงไม่สูญเสียข้อมูลd-- ทศนิยม 64 บิต (ความแม่นยำคู่; ประมาณสิบห้าหลักทศนิยม) การบรรจุfloat32 บิตของ MicroPython ลงในdจะขยายเป็นแปดไบต์แต่ไม่เพิ่มความแม่นยำs-- สตริงไบต์ความยาวคงที่ นำหน้าด้วยจำนวน (8sหมายถึงฟิลด์แปดไบต์)
2.29.2. ลำดับไบต์¶
จำนวนเต็มหลายไบต์สามารถเก็บในหน่วยความจำได้สองแบบ ตัวเลข 0x12345678 ในฟิลด์ 32 บิตมีเลย์เอาต์ดังนี้:
Little-endian -- ไบต์ที่มีนัยสำคัญน้อยที่สุดอยู่ก่อน:
78 56 34 12Big-endian -- ไบต์ที่มีนัยสำคัญมากที่สุดอยู่ก่อน:
12 34 56 78
ทั้งสองแบบเข้ารหัสค่าเดียวกัน แต่ไม่ตกลงกันว่าปลายใดของฟิลด์คือไบต์ต่ำ ไฟล์ที่เขียนโดยระบบหนึ่งจะอ่านผิดเพี้ยนเมื่ออ่านโดยอีกระบบหากลำดับไบต์ไม่ตรงกัน
อักขระนำหน้าของสตริงรูปแบบกำหนดลำดับ:
<-- little-endian พบบ่อยบน x86 และ ARM>-- big-endian พบบ่อยในโปรโตคอลเครือข่าย!-- ลำดับเครือข่าย เทียบเท่ากับ>
หากไม่มีอักขระนำหน้า จะใช้ลำดับไบต์และการจัดตำแหน่งแบบ native การระบุ < หรือ > อย่างชัดเจนจะขจัดความกำกวมนั้น และมักเป็นสิ่งที่ต้องการเมื่ออ่านไฟล์หรือสื่อสารกับเครื่องอื่น
Note
OpenMV Cam เป็นแบบ little-endian -- เหมือนกับ PC host ใช้ < ในสตริงรูปแบบสำหรับไฟล์ในกล้องและข้อมูลไบนารีที่รับส่งกับเดสก์ท็อป ใช้ > (หรือ !) สำหรับโปรโตคอลเครือข่ายและรูปแบบใดก็ตามที่ข้อกำหนดกำหนดให้ใช้ big-endian
"<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 -- ให้ถือว่านั่นคือเงื่อนไขสิ้นสุดสตรีมแทนที่จะพยายามแกะเรคคอร์ดที่ไม่สมบูรณ์