2.29. Struct та бінарні дані¶
Модуль struct пакує значення Python у фіксований бінарний формат і розпаковує байти назад у значення Python. Використовуйте його під час роботи з бінарним форматом файлу, мережевим протоколом або пристроєм, що обмінюється записами фіксованого розміру.
Більшість випадків покривають дві функції:
struct.pack()– приймає значення Python і рядок формату, повертає об’єктbytesіз точним розташуванням.struct.unpack()– приймає рядок формату та об’єктbytes, повертає кортеж значень Python.
2.29.1. Рядки формату¶
Рядок формату містить один код на кожне поле у записі. Коди описують як розмір, так і інтерпретацію кожного поля.
Клас int у Python не має фіксованого розміру – він зростає відповідно до присвоєного значення. Бінарні формати мають фіксовані розміри: кожне цілочисельне поле використовує узгоджену кількість байтів. Модуль struct перетворює необмежені цілі числа Python на ці представлення фіксованого розміру.
Ширина цілого числа – це кількість бітів, які воно використовує. Один байт – вісім бітів. Код у нижньому регістрі відповідає знаковому варіанту; код у верхньому регістрі – беззнаковому (лише невід’ємні значення):
b/B– 8-бітне (один байт).-128..127знакове,0..255беззнакове.h/H– 16-бітне (два байти).-32768..32767знакове,0..65535беззнакове.i/I– 32-бітне (чотири байти). Приблизно ±два мільярди знакове, чотири мільярди беззнакове.q/Q– 64-бітне (вісім байтів). Практично необмежене для повсякденного використання.
Обирайте ширину, яка комфортно перекриває очікуваний діапазон. Пакування значення поза оголошеним діапазоном або мовчки переповнює його, або викидає struct.error, залежно від збірки.
Інші поширені коди стосуються чисел із рухомою комою та рядків байтів:
f– 32-бітне число з рухомою комою (одинарна точність; приблизно сім десяткових цифр). Звичайнийfloatу MicroPython вже має цей розмір, тому пакування вfє без втрат.d– 64-бітне число з рухомою комою (подвійна точність; приблизно п’ятнадцять десяткових цифр). Пакування 32-бітногоfloatу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. Поширений у мережевих протоколах.!– мережевий порядок, еквівалентний>.
Без першого символу використовуються власний порядок байтів і вирівнювання платформи; явне задання < або > усуває цю неоднозначність і зазвичай є кращим варіантом під час читання файлу або обміну даними з іншою машиною.
Примітка
OpenMV Cam використовує little-endian – так само, як і ПК-хост. Використовуйте < у рядках формату для файлів, локальних до камери, і для бінарних даних, що передаються до або з комп’ютера. Використовуйте > (або !) для мережевих протоколів і для будь-якого формату, специфікація якого вимагає 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() завжди повертає кортеж, навіть якщо формат описує одне поле. Розпаковуйте його в одному рядку для зручності читання.
2.29.5. Рядки байтів фіксованої довжини¶
Код s читає або записує фрагмент байтів дослівно. Кількість вказується перед s – 4s означає «чотири байти, оброблені як єдиний рядок байтів». Це звичайний спосіб вставити магічне значення, мітку фіксованого розміру або поле імені з доповненням у запис:
header = struct.pack("<4sHI", b"OMV0", 320, 1000000)
print(header)
Виведення:
b'OMV0@\x01@B\x0f\x00'
Перші чотири байти – це літеральна магія 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 – сприймайте це як умову кінця потоку, а не намагайтеся розпакувати неповний запис.