Curso Avançado de Desenvolvimento Python
Capítulo
>
Nível
Módulos de Serialização
Módulo de Estruturas
Objetivo
Configure a divisão final dos dados para a nova fazenda usando o módulo struct.
No fim da estrada há um posto de serviços que administra a nova fazenda já construída e as culturas já plantadas. Aqui, iremos inspecionar e processar os dados das culturas já plantadas e a produção projetada da fazenda. Como em outros níveis deste capítulo, estaremos trabalhando com serialização e desserialização de dados, introduzindo um módulo final chamado struct.
O módulo struct introduz uma série de funções de serialização que empacota dados em formato binário. Diferente dos outros módulos com os quais trabalhamos, porém, você tem um controle maior sobre como podemos estruturar os dados tanto quando estão serializados quanto quando posteriormente são desserializados, tornando-o mais versátil do que outros módulos. Use import struct para acessar as seguintes funções que iremos utilizar para processar os dados:
struct.calcsize(): Determina quantos bytes um dado formato de string ocupa ao ser empacotado; ela recebe um (1) argumento sendo o formato que você deseja verificar o seu tamanho em bytes. Os formatos que usaremos são:- integer: representado como
'i', é o formato para dados representados por números inteiros - float: representado como
'f', é o formato para dados representados por números decimais - double: representado como
'd', é o formato para dados representados por números decimais mais complexos, onde o formato float é insuficiente.
- integer: representado como
struct.pack(): Serializa dados em formato binário, empacotando-os em um formato de sua escolha. Você pode passar dois (2) ou mais argumentos, sendo o formato que você deseja usar e os valores que você gostaria de serializar. Os formatos são os mesmos mencionados anteriormente e você deve adicioná-los de acordo com a quantidade de argumentos que passar.struct.unpack(): Desserializa dados binários empacotados, recebe dois (2) argumentos, o formato no qual ele precisa ser compatível com o formato em que os dados foram serializados, e o segundo é os dados que foram serializados.struct.iter_unpack(): Desserializa dados binários empacotados, funciona da mesma forma questruct.unpack(), mas itera através de cada bloco de dados individualmente utilizando um loop.struct.pack_into(): Uma versão avançada destruct.pack(), recebe quatro (4) argumentos: o formato que você deseja usar, o buffer de dados no qual deseja inserir os dados, a posição no buffer que você deseja que os dados ocupem e, por fim, os dados que você está empacotando.struct.unpack_from(): Uma versão avançada destruct.unpack(), recebe três (3) argumentos: o formato que você deseja usar, o buffer de dados do qual deseja desserializar e, por fim, a localização no buffer de onde você deseja desserializar. Isso permite que você desserialize porções específicas dos dados.
Caminhe até a marca X com luz no posto de serviços e encare a mesa, crie três (3) variáveis chamadas: integer, float e double. Usaremos estas para verificar o tamanho em bytes de cada formato utilizando a função struct.calcsize(). Para a variável integer utilize a função com 'i' como argumento, para a variável float utilize a função com 'f' como argumento e, por fim, para a variável double utilize a função com 'd' como argumento. Por exemplo: integer = struct.calcsize('i'). Adicione as três (3) variáveis à função write() pré-existente.
Caminhe até a marca X dourada e utilize a função read() para coletar dados sobre a nova fazenda; tome nota dos pontos de dados e dos formatos para uso futuro, a saber: Resources, Size e Estimate. Uma vez que você tenha anotado esses dados, caminhe até a marca X com luz sobre o tapete azul e crie uma variável chamada blue_data.
Com a variável blue_data armazene o valor da função struct.pack(), definindo como argumentos o formato e os valores que você anotou anteriormente. Ao escrever o formato, você deve "empacotar" os tipos de formato em uma única unidade. Por exemplo, o formato integer é representado por 'i' conforme detalhado anteriormente; se você estiver adicionando três tipos de dados inteiros, você os adiciona como 'iii'. Da mesma forma, se você estiver adicionando um integer, um float e um double, seria escrito 'ifd'. Ao adicionar os dados, você os coloca individualmente como argumentos. Veja um exemplo geral:
data_1 = 8 # é um inteiro data_2 = 2.25 # é um float data_3 = 900.702938103 # é um double blue_data = struct.pack('ifd', data_1, data_2, data_3)
A função struct.pack() permite muita flexibilidade, permitindo que você defina como deseja formatar os dados e permitindo que você serialize múltiplos pontos de dados de uma só vez, a seu critério. Adicione os dados lidos anteriormente usando o exemplo acima como base. Use a função display() com blue_data como argumento para visualizar os dados empacotados.
Caminhe até a marca X escura sobre o tapete azul e encare o terminal, crie uma variável chamada blue_unpack e armazene o valor da função struct.unpack(), adicionando o mesmo formato usado para empacotar os dados e a variável blue_data como argumentos. O formato é escrito da mesma maneira que na função struct.pack(), assim você pode desserializar os dados da mesma forma que foram serializados inicialmente. Use a função write() com blue_unpack como argumento para verificar os dados que você empacotou anteriormente.
No mesmo local, também utilizaremos a função struct.iter_unpack(). Ela recebe os mesmos argumentos exatos de struct.unpack(), mas agora está formatada como um loop for, o que nos permite iterar pelos dados em vez de simplesmente escrevê-los todos. A função é codificada da seguinte forma:
for values in struct.iter_unpack(-insert value-, -insert value-): player.speak(values)
Utilizaremos a função speak() para exibir os valores; para os argumentos que faltam na função struct.iter_unpack(), adicione os mesmos utilizados anteriormente com struct.unpack(), pois estas são variações da mesma função, quanto ao uso.
Caminhe até a marca X dourada sobre o tapete vermelho e encare a mesa; utilize a função read() para revisar as quantidades das culturas. Anote todas as quantidades e o formato para cada cultura. Caminhe até a marca X com luz sobre o tapete vermelho e crie um objeto chamado buffer, e atribua a ele o valor de bytearray(16). Esta é uma coleção de bytes que podemos endereçar antes de preenchê-la; para nossos propósitos, ela funciona como um banco de dados onde você pode armazenar os dados manualmente. O número 16 passado como argumento é o comprimento, ou seja, a quantidade de bytes que você pode armazenar no objeto buffer.
Utilize 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 valor da função, pois a função inserirá os valores diretamente no objeto buffer. Vamos utilizar a função para cada um dos valores que você anotou anteriormente, populando seus argumentos com os dados correspondentes.
Por exemplo, para o primeiro valor da cultura fornecido, você receberá o seu formato, a posição em bytes e a sua quantidade. Adicione o formato como o primeiro argumento, o local onde você deseja serializar os bytes, que neste caso é buffer. Para o terceiro argumento, defina a posição onde você deseja adicionar o valor dentro do buffer; o formato determina os bytes ocupados, então tome cuidado para empacotar dados que não estejam previamente ocupados. Por fim, adicione o valor que você deseja serializar e empacotar no objeto buffer.
struct.pack_into('i', buffer, 0, 82)
O objeto buffer tem 16 bytes, conforme declarado anteriormente; no código fornecido acima, o formato integer ocupa 4 bytes e é inserido na posição 0. Isso significa que, uma vez que os dados são inseridos no buffer, restam apenas 12 bytes livres, com as posições 0 a 3 sendo ocupadas pelo valor fornecido, neste caso, 82. Faça isso para todos os pontos de dados das culturas anotados anteriormente, totalizando três (3). Utilize a função display() e passe buffer para exibir os dados serializados.
Caminhe até a marca X escura sobre o tapete vermelho e encare o terminal. Aqui, iremos 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 os dados de cada ponto de dados individualmente. Para cada variável, armazene o valor de struct.unpack_from() e passe como argumentos os mesmos dados que você anotou para o empacotamento. Para o primeiro argumento, defina o formato, para o segundo, adicione o objeto buffer, que é o local de onde os dados serão desserializados, e, por fim, adicione a posição no buffer da qual você deseja desserializar. Por exemplo:
lettuce = struct.unpack_from('i', buffer, 0)
Estes dados correspondem ao exemplo de empacotamento anterior sendo desserializado; faça o mesmo para as outras duas variáveis e insira lettuce, carrots e melons na função write() pré-existente para completar o nível.