Curso Avançado de Desenvolvimento em Python
Capítulo
>
Nível

Módulos de Serialização
Módulo Struct

Objetivo

Configure a distribuição final dos dados para a nova fazenda usando o módulo struct.

No final da estrada há um posto de serviço que administra a nova fazenda já construída e as plantações já semeadas. Aqui vamos inspecionar e processar os dados das culturas já plantadas e a produção projetada da fazenda. Assim como nos outros níveis deste capítulo, trabalharemos com serialização e desserialização de dados, apresentando um módulo final chamado struct.

O módulo struct introduz uma série de funções de serialização que empacotam dados em formato binário; ao contrário dos outros módulos com que trabalhamos, aqui você tem maior controle sobre como estruturar os dados tanto na serialização quanto na desserialização, tornando-o mais versátil. Use import struct para acessar as seguintes funções que usaremos para processar os dados:

  • struct.calcsize(): determina quantos bytes são ocupados por uma dada string de formato; recebe um (1) argumento, que é o formato cuja quantidade de bytes você deseja verificar. Os formatos que usaremos são:
    • integer: representado como 'i', é o formato para dados em números inteiros
    • float: representado como 'f', é o formato para dados em números decimais
    • double: representado como 'd', é o formato para dados em números decimais de maior precisão, quando o formato float é insuficiente.
  • struct.pack(): serializa dados em binário, empacotados em um formato à sua escolha. Recebe dois (2) ou mais argumentos: o formato que deseja usar e os valores que deseja serializar. Os formatos são os já descritos e devem corresponder à quantidade de argumentos que você fornecer.
  • struct.unpack(): desserializa dados binários empacotados; recebe dois (2) argumentos: o formato comparável ao usado na serialização e os dados empacotados.
  • struct.iter_unpack(): desserializa dados binários empacotados; funciona como struct.unpack(), mas itera por cada bloco de dados individualmente usando um loop.
  • struct.pack_into(): uma versão avançada de struct.pack(); recebe quatro (4) argumentos: o formato que deseja usar, o buffer de dados onde inserir, a posição no buffer para ocupar e, por fim, os dados a serem empacotados.
  • struct.unpack_from(): uma versão avançada de struct.unpack(); recebe três (3) argumentos: o formato a usar, o buffer de dados de onde desserializar e a posição no buffer de onde iniciar a desserialização. Isso permite extrair porções específicas dos dados.

Ao chegar à marca X iluminada no posto de serviço e de frente para a mesa, crie três (3) variáveis chamadas: integer, float e double. Usaremos essas variáveis para verificar o tamanho em bytes de cada formato, usando a função struct.calcsize(). Para a variável integer, use a função com 'i' como argumento; para float, use 'f'; e, por fim, para double, use 'd'. Por exemplo: integer = struct.calcsize('i'). Adicione as três (3) variáveis à função pré-escrita write().

Vá até a marca X dourada e use a função read() para coletar dados sobre a nova fazenda; anote os pontos de dados e seus formatos para uso futuro, a saber: Resources, Size e Estimate. Assim que fizer isso, dirija-se à marca X iluminada sobre o tapete azul e crie uma variável chamada blue_data.

Na variável blue_data, armazene o valor retornado por struct.pack(), passando como argumentos o formato e os valores anotados anteriormente. Ao escrever o formato, você deve “empacotar” os tipos de formato em uma única string. Por exemplo, o formato inteiro é representado por 'i'; se você estiver adicionando três inteiros, escreva 'iii'. Da mesma forma, se estiver adicionando um inteiro, um float e um double, use 'ifd'. Ao adicionar os dados, passe-os individualmente como argumentos. Veja um exemplo completo:

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)

A função struct.pack() oferece muita flexibilidade, permitindo que você defina como formatar os dados e serializar vários pontos de dados de uma vez, conforme sua necessidade. Adicione os dados previamente lidos usando o exemplo acima como base. Use a função display() com blue_data como argumento para ver os dados empacotados.

Dirija-se à marca X escura sobre o tapete azul e fique de frente para o terminal; crie uma variável chamada blue_unpack e armazene o valor de struct.unpack(), usando o mesmo formato usado para empacotar os dados e a variável blue_data como argumentos. O formato é escrito da mesma forma que em struct.pack(), para que você possa desserializar os dados da mesma maneira que os serializou. Use a função write() com blue_unpack como argumento para verificar os dados empacotados anteriormente.

No mesmo local, também usaremos a função struct.iter_unpack(), que recebe os mesmos argumentos que struct.unpack(), mas agora é usada dentro de um loop for, permitindo iterar pelos dados em vez de escrevê-los de uma só vez. A função é codificada da seguinte forma:

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

Usaremos a função speak() para exibir os valores. Para os argumentos faltantes em struct.iter_unpack(), insira os mesmos usados anteriormente em struct.unpack(), pois são variações da mesma função em termos de uso.

Vá até a marca X dourada sobre o tapete vermelho e fique de frente para a mesa; use a função read() para revisar as quantidades das culturas. Anote todas as quantidades e os formatos de cada cultura. Em seguida, dirija-se à marca X iluminada sobre o tapete vermelho e crie um objeto chamado buffer, atribuindo a este o valor bytearray(16). Isso é uma coleção de bytes que podemos manipular antes de preenchê-la; para nossos propósitos, funciona como um banco de dados onde você pode armazenar dados manualmente. O número 16 como argumento indica quantos bytes você pode armazenar no objeto buffer.

Use a função struct.pack_into() para preencher o objeto buffer que você criou. Não é necessário criar uma variável para armazenar o retorno da função, pois ela insere os valores diretamente no objeto buffer. Vamos usar essa função para cada um dos valores que você anotou anteriormente, preenchendo seus argumentos.

Por exemplo, para o primeiro valor de cultura fornecido, você terá seu formato, sua posição em bytes e sua quantidade. Adicione o formato como primeiro argumento, o buffer (buffer) como segundo, a posição dentro do buffer como terceiro — o formato determina quantos bytes são ocupados, então certifique-se de não sobrescrever dados já inseridos — e, por fim, a quantidade a ser empacotada.

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

O objeto buffer tem 16 bytes, como mencionado anteriormente. No código acima, o formato inteiro ocupa 4 bytes e é inserido na posição 0. Isso significa que, após inserir esse dado, restam apenas 12 bytes disponíveis no buffer, pois as posições 0 a 3 foram ocupadas pelo valor 82. Faça o mesmo para todos os pontos de dados das culturas anotados anteriormente; são três (3) no total. Use a função display() com buffer como argumento para exibir os dados serializados.

Dirija-se à marca X escura sobre o tapete vermelho e fique de frente para o terminal; aqui vamos desserializar os dados para verificar o conteúdo e garantir o armazenamento correto. Crie três variáveis chamadas lettuce, carrots e melons; iremos desserializar cada ponto de dados individualmente. Para cada variável, armazene o valor de struct.unpack_from(), usando como argumentos os dados que você anotou ao empacotar. No primeiro argumento, defina o formato; no segundo, adicione o objeto buffer, que é o local de onde desserializar; e, por fim, adicione a posição no buffer de onde extrair. Por exemplo:

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

Este comando corresponde ao exemplo de empacotamento anterior sendo desserializado; faça o mesmo para as outras duas variáveis e passe lettuce, carrots e melons para a função pré-escrita write() para completar o nível.

Livro de Código