Продвинутый курс разработки на Python
Глава
>
Уровень

Модули сериализации
Модуль Struct

Цель

Настройте окончательный разбор данных для нового поля с помощью модуля struct.

На конце дороги находится сервисная станция, которая управляет вновь построенными полями и уже посаженными культурами. Здесь мы будем анализировать и обрабатывать данные о уже посаженных культурах и прогнозируемой урожайности полей. Как и в других уровнях этой главы, мы будем работать с сериализацией и десериализацией данных, представив финальный модуль — модуль struct.

Модуль struct предоставляет набор функций сериализации, которые упаковывают данные в бинарном формате. В отличие от других модулей, с которыми мы работали, он позволяет более гибко задавать структуру данных при их упаковке и распаковке, что делает его более универсальным. Используйте import struct, чтобы получить доступ к следующим функциям, которые мы будем использовать для обработки данных:

  • struct.calcsize(): определяет, сколько байт занимает упакованная строка формата; принимает один (1) аргумент — формат, размер в байтах которого нужно определить. Форматы, которые мы будем использовать:
    • integer: представлено как 'i', формат для целых чисел
    • float: представлено как 'f', формат для чисел с плавающей точкой
    • double: представлено как 'd', формат для более точных чисел с плавающей точкой, когда float недостаточен
  • struct.pack(): сериализует данные в бинарном виде, упаковывая их в указанном формате. Функция принимает два (2) или более аргумента: сначала формат, затем значения для сериализации. Форматы — те, что были описаны выше, и их нужно указывать столько раз, сколько значений вы передаёте.
  • struct.unpack(): десериализует упакованные двоичные данные; принимает два (2) аргумента: формат, в котором данные были упакованы, и сами упакованные данные.
  • struct.iter_unpack(): десериализует упакованные двоичные данные так же, как struct.unpack(), но возвращает итератор для последовательного обхода каждого блока данных в цикле.
  • struct.pack_into(): расширенная версия struct.pack(); принимает четыре (4) аргумента: формат, буфер данных, позицию в буфере, по которой нужно записать данные, и сами данные для упаковки.
  • struct.unpack_from(): расширенная версия struct.unpack(); принимает три (3) аргумента: формат, буфер данных и смещение (позицию) в буфере, с которой нужно начать распаковку. Это позволяет извлекать определённые части данных.

Подойдите к светлой отметке X на сервисной станции и встаньте лицом к столу. Создайте три (3) переменные с именами: integer, float и double. Мы будем использовать их, чтобы определить размер каждого формата в байтах с помощью функции struct.calcsize(). Для переменной integer вызовите функцию с аргументом 'i', для float — с аргументом 'f', и для double — с аргументом 'd'. Например: integer = struct.calcsize('i'). Добавьте эти три (3) переменные в уже написанную функцию write().

Перейдите к золотой отметке X и используйте функцию read(), чтобы получить данные о новом поле. Запишите полученные показатели и их форматы для дальнейшего использования, а именно: Resources, Size и Estimate. Когда сделаете заметки, вернитесь к светлой отметке X на синем ковре и создайте переменную blue_data.

В переменной blue_data сохраните результат работы функции struct.pack(), передав в неё формат и значения, которые вы записали ранее. При указании формата нужно «упаковать» все типы форматов в одну строку. Например, формат целого числа обозначается как 'i'; если вы упаковываете три целых числа, формат будет 'iii'. Аналогично, если вы упаковываете целое число, число с плавающей точкой и двойную точность, формат будет 'ifd'. Значения передаются как отдельные аргументы. Вот пример:

data_1 = 8 # is an integer data_2 = 2.25 # is a float data_3 = 900.702938103 # is a double blue_data = struct.pack('ifd', data_1, data_2, data_3)

Функция struct.pack() предоставляет большую гибкость, позволяя задавать формат данных и сериализовать несколько значений одновременно. Добавьте данные, которые вы получили ранее, используя приведённый выше пример в качестве основы. Используйте функцию display() с аргументом blue_data, чтобы увидеть упакованные данные.

Перейдите к тёмной отметке X на синем ковре и встаньте у терминала. Создайте переменную blue_unpack и сохраните в ней результат работы функции struct.unpack(), передав те же формат и blue_data, которые вы использовали при упаковке. Формат указывается так же, как в struct.pack(), чтобы распаковка соответствовала упаковке. Используйте функцию write() с аргументом blue_unpack, чтобы проверить ранее упакованные данные.

В том же месте мы также используем функцию struct.iter_unpack(): она принимает те же аргументы, что и struct.unpack(), но возвращает итератор, позволяющий обрабатывать данные в цикле вместо единовременной распаковки. Код выглядит так:

for values in struct.iter_unpack(-insert value-, -insert value-): player.speak(values)

Мы будем использовать функцию speak() для вывода значений. В качестве аргументов для struct.iter_unpack() укажите те же, что вы использовали в struct.unpack(), так как это вариации одной и той же функции.

Перейдите к золотой отметке X на красном ковре и встаньте за столом. Используйте функцию read(), чтобы получить количество каждой культуры. Запишите все количества и формат для каждой культуры. Затем перейдите к светлой отметке X на красном ковре и создайте объект buffer, присвоив ему значение bytearray(16). Это массив байтов, к которому можно обращаться перед заполнением; по сути он служит хранилищем данных, куда вы сможете вручную записывать значения. Число 16 задаёт длину массива байтов, которую можно хранить в buffer.

Используйте функцию struct.pack_into(), чтобы заполнить созданный объект buffer. Переменную для хранения результата создавать не нужно — функция сразу вставляет данные в buffer. Мы будем вызывать её для каждого из значений, которые вы ранее записали, подставляя их в аргументы.

Например, для первой культуры вам известен её формат, позиция в байтах и количество. В качестве первого аргумента укажите формат, вторым — объект buffer, в который будут записаны байты. Третий аргумент — смещение (позиция) внутри buffer, где начнётся запись; формат определяет, сколько байт займут данные, поэтому следите, чтобы не перезаписать уже занятые области. В четвёртый аргумент передайте само значение для сериализации и упаковки в buffer.

struct.pack_into('i', buffer, 0, 82)

Как уже было сказано, объект buffer содержит 16 байт. В приведённом выше примере формат целого числа занимает 4 байта и записывается на позиции 0. Значит, после этого в buffer остаётся 12 свободных байт: позиции 0–3 заняты значением 82. Проделайте то же самое для всех трёх (3) точек данных о культурах, которые вы зафиксировали ранее. Используйте функцию display() с аргументом buffer, чтобы отобразить сериализованные данные.

Перейдите к тёмной отметке X на красном ковре и подойдите к терминалу. Здесь мы будем десериализовывать данные, чтобы проверить их содержимое и убедиться, что всё записано правильно. Создайте три переменные: lettuce, carrots и melons. Мы будем распаковывать данные каждой точки отдельно. Для каждой переменной сохраните результат работы struct.unpack_from(), передав те же данные, которые вы использовали при упаковке. Первым аргументом укажите формат, вторым — объект buffer, из которого нужно читать, и последним — смещение (позицию) в buffer. Например:

lettuce = struct.unpack_from('i', buffer, 0)

Этот пример показывает распаковку данных из предыдущего примера. Сделайте то же самое для двух других переменных и передайте lettuce, carrots и melons в уже написанную функцию write(), чтобы завершить уровень.

Книга Кода