Curso de Desarrollo Avanzado en Python
Capítulo
>
Nivel

Módulos de serialización
Módulo Struct

Objetivo

Configurar el desglose final de datos para la nueva granja utilizando el módulo struct.

En el extremo de la carretera hay una estación de servicio que administra la nueva granja que ya está construida y los cultivos ya plantados. Aquí inspeccionaremos y procesaremos los datos de los cultivos ya plantados y la producción proyectada de la granja. Al igual que con otros niveles de este capítulo, trabajaremos con la serialización y deserialización de datos, introduciendo un módulo final llamado struct.

El módulo struct introduce una serie de funciones de serialización que empaquetan datos en formato binario; a diferencia de otros módulos con los que hemos trabajado, sin embargo, tienes mayor control de cómo estructuramos los datos tanto al serializarlos como al deserializarlos, lo que lo hace más versátil que otros módulos. Usa import struct para acceder a las siguientes funciones que utilizaremos para procesar los datos:

  • struct.calcsize(): Determina cuántos bytes ocupa una cadena de formato dada; toma un (1) argumento, el formato cuyo tamaño en bytes deseas verificar. Los formatos que usaremos son:
    • integer: representado como 'i', es el formato para datos representados en números enteros
    • float: representado como 'f', es el formato para datos representados en números decimales
    • double: representado como 'd', es el formato para datos representados en números decimales más complejos donde el formato float es insuficiente.
  • struct.pack(): Serializa datos en binario, empaquetados con el formato de tu elección. Puede tomar dos (2) o más argumentos: el formato que deseas usar y los valores que quieres serializar. Los formatos son los anteriormente descritos y debes agregarlos según la cantidad de argumentos que incluyas.
  • struct.unpack(): Deserializa datos binarios empaquetados; toma dos (2) argumentos: el formato con el que debe coincidir en comparación al que fue serializado y, en segundo lugar, los datos que fueron serializados.
  • struct.iter_unpack(): Deserializa datos binarios empaquetados; funciona igual que struct.unpack(), pero itera a través de cada bloque de datos individualmente mediante un bucle.
  • struct.pack_into(): Una versión avanzada de struct.pack(); toma cuatro (4) argumentos: el formato que deseas usar, el búfer de datos en el que quieres insertar los datos, la posición en el búfer que deseas ocupar y, por último, los datos que vas a empaquetar.
  • struct.unpack_from(): Una versión avanzada de struct.unpack(); toma tres (3) argumentos: el formato que deseas usar, el búfer de datos del que quieres deserializar y, finalmente, la ubicación en el búfer de la que deseas deserializar. Esto te permite deserializar porciones específicas de los datos.

Camina hasta la marca X iluminada en la estación de servicio y ponte frente al escritorio; crea tres (3) variables llamadas: integer, float y double. Usaremos estas para verificar el tamaño en bytes de cada formato usando la función struct.calcsize(). Para la variable integer, usa la función con 'i' como argumento; para la variable float, usa la función con 'f' como argumento; y finalmente, para la variable double, usa la función con 'd' como argumento. Por ejemplo: integer = struct.calcsize('i'). Agrega las tres (3) variables a la función preescrita write().

Camina hasta la marca X dorada y usa la función read() para recopilar datos sobre la nueva granja; toma nota de los puntos de datos y el formato para uso futuro, a saber: Resources, Size y Estimate. Una vez que tomes nota de esto, camina a la marca X iluminada sobre la alfombra azul y crea una variable llamada blue_data.

Con la variable blue_data, guarda el valor de la función struct.pack(), estableciendo como argumentos el formato y los valores que anotaste previamente. Al escribir el formato, debes “empaquetar” los tipos de formato en una sola unidad. Por ejemplo, el formato integer está etiquetado como 'i'; si agregas tres enteros, los pones como 'iii'. De la misma forma, si agregas un entero, un float y un double, se escribe 'ifd'. Al agregar los datos, colócalos individualmente como argumentos. Aquí hay un ejemplo general:

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)

La función struct.pack() ofrece mucha flexibilidad, permitiéndote definir cómo quieres formatear los datos y serializar varios puntos de datos a la vez, a tu discreción. Agrega los datos previamente leídos usando el ejemplo anterior como base. Usa la función display() con blue_data como argumento para ver los datos empaquetados.

Camina a la marca X oscura sobre la alfombra azul y ponte frente al terminal; crea una variable llamada blue_unpack y guarda el valor de la función struct.unpack(), agregando el mismo formato usado para empaquetar los datos y la variable blue_data como argumentos. El formato se escribe de la misma manera que en struct.pack(), de modo que puedas deserializar los datos igual que los serializaste. Usa la función write() con blue_unpack como argumento para verificar los datos que empaquetaste antes.

En la misma ubicación también usaremos la función struct.iter_unpack(). Toma los mismos argumentos que struct.unpack(), pero ahora se formatea como un bucle for, lo que nos permite iterar por los datos en lugar de escribirlos todos de una vez. La función se codifica así:

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

Usaremos la función speak() para mostrar los valores. Para los argumentos faltantes en struct.iter_unpack(), añade los mismos que usaste previamente con struct.unpack(), ya que son variaciones de la misma función en cuanto a uso.

Camina a la marca X dorada sobre la alfombra roja y ponte frente al escritorio; usa la función read() para revisar las cantidades de los cultivos. Toma nota de todas las cantidades y el formato de cada cultivo. Camina a la marca X iluminada sobre la alfombra roja y crea un objeto llamado buffer, asignándole el valor bytearray(16). Esto es una colección de bytes a la que podemos acceder antes de poblarla; para nuestros propósitos, funciona como un banco de datos donde puedes almacenar manualmente la información. El número 16 como argumento es la longitud de bytes que puedes almacenar en el objeto buffer.

Usa la función struct.pack_into() para poblar el objeto buffer que creaste. No es necesario crear una variable para almacenar el valor de la función, ya que ésta insertará directamente los valores en el objeto buffer. Vamos a usar la función para cada uno de los valores que anotaste previamente y completar sus argumentos con ellos.

Por ejemplo, para el primer valor de cultivo proporcionado, se te indicará su formato, su posición en bytes y su cantidad. Agrega el formato como primer argumento, luego la ubicación a la que deseas serializar los bytes (en este caso buffer). Como tercer argumento, indica la posición en la que quieres añadir el valor dentro de buffer; el formato determina los bytes ocupados, así que asegúrate de empaquetar datos sin superponerlos. Por último, añade el valor que deseas serializar y empaquetar en el objeto buffer.

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

El objeto buffer tiene 16 bytes, como se indicó anteriormente. En el código anterior, el formato integer ocupa 4 bytes y se inserta en la posición 0. Esto significa que, una vez insertados los datos en buffer, solo quedan 12 bytes sin usar (las posiciones 0 a 3 están ocupadas por el valor 82). Haz esto para los tres (3) puntos de datos de cultivo que anotaste previamente. Usa la función display() y pasa buffer para mostrar los datos serializados.

Camina a la marca X oscura sobre la alfombra roja y ponte frente al terminal; aquí deserializaremos los datos para verificar el contenido y asegurar un almacenamiento correcto. Crea tres variables llamadas: lettuce, carrots y melons; deserializaremos los datos de cada punto de datos individualmente. Para cada variable, almacena el valor de struct.unpack_from() y establece como argumentos los mismos datos que usaste para empaquetar. Como primer argumento, pon el formato; como segundo, agrega el objeto buffer (desde donde se deserializa); y, finalmente, añade la posición en buffer desde la que quieres deserializar. Por ejemplo:

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

Estos datos corresponden al ejemplo de empaquetado anterior al deserializarse. Haz lo mismo para las otras dos variables e inserta lettuce, carrots y melons en la función preescrita write() para completar el nivel.

Libro de Código