2.29. Struct e dados binários¶
O módulo struct empacota valores Python em um layout binário de tamanho fixo e desempacota bytes de volta para valores Python. Recorra a ele ao trabalhar com um formato de arquivo binário, um protocolo de rede ou um dispositivo que troca registros de tamanho fixo.
Duas funções cobrem a maioria dos casos:
struct.pack()– recebe valores Python e uma string de formato e retorna um objetobytescom o layout exato.struct.unpack()– recebe uma string de formato e um objetobytese retorna uma tupla de valores Python.
2.29.1. Strings de formato¶
Uma string de formato lista um código por campo do registro. Os códigos descrevem tanto o tamanho quanto a interpretação de cada campo.
O tipo int do Python não tem tamanho fixo – ele cresce para acomodar qualquer valor que você atribuir. Os formatos binários têm, sim, tamanhos fixos: cada campo inteiro usa um número acordado de bytes. O módulo struct converte entre os ints ilimitados do Python e essas representações de tamanho fixo.
A largura de um inteiro é o número de bits que ele utiliza. Um byte são oito bits. O código em minúscula é a variante com sinal; o código em maiúscula é a variante sem sinal (apenas valores não negativos):
b/B– 8 bits (um byte).-128..127com sinal,0..255sem sinal.h/H– 16 bits (dois bytes).-32768..32767com sinal,0..65535sem sinal.i/I– 32 bits (quatro bytes). Cerca de ±dois bilhões com sinal, quatro bilhões sem sinal.q/Q– 64 bits (oito bytes). Efetivamente ilimitado para o uso cotidiano.
Escolha uma largura que cubra com folga o intervalo que você espera. Empacotar um valor fora do intervalo declarado faz com que ele dê a volta (wrap) silenciosamente ou lança struct.error, dependendo da build.
Os demais códigos comuns são para floats e strings de bytes:
f– float de 32 bits (precisão simples; cerca de sete dígitos decimais). O tipofloatcomum do Python no MicroPython já tem esse tamanho, então empacotar um emfnão tem perdas.d– float de 64 bits (precisão dupla; cerca de quinze dígitos decimais). Empacotar umfloatde 32 bits do MicroPython emdo amplia para oito bytes, mas não adiciona precisão.s– string de bytes de tamanho fixo, precedida por uma contagem (8spara um campo de oito bytes).
2.29.2. Ordem dos bytes¶
Um inteiro de múltiplos bytes pode ser armazenado na memória de duas formas. O número 0x12345678 em um campo de 32 bits é organizado assim:
Little-endian – o byte menos significativo primeiro:
78 56 34 12.Big-endian – o byte mais significativo primeiro:
12 34 56 78.
Ambos codificam o mesmo valor; eles só discordam sobre qual extremidade do campo contém o byte de menor ordem. Um arquivo escrito por um sistema fica corrompido quando lido pelo outro se a ordem dos bytes não corresponder.
O caractere inicial da string de formato escolhe a ordem:
<– little-endian. Comum em x86 e ARM.>– big-endian. Comum em protocolos de rede.!– ordem de rede, equivalente a>.
Sem um caractere inicial, são usados a ordem de bytes nativa e o alinhamento nativo; definir < ou > explicitamente remove essa ambiguidade e é geralmente o que você quer ao ler um arquivo ou ao se comunicar com outra máquina.
Nota
A OpenMV Cam é little-endian – o mesmo que seu PC host. Use < nas strings de formato para arquivos locais da câmera e para dados binários que trafegam de ou para um desktop. Use > (ou !) para protocolos de rede e para qualquer formato cuja especificação exija big-endian.
"<HI" empacota um valor de 16 bits seguido de um valor de 32 bits em seis bytes little-endian.¶
2.29.3. Empacotamento¶
import struct
blob = struct.pack("<HI", 320, 1000000)
print(blob, len(blob))
Saída:
b'@\x01@B\x0f\x00' 6
O formato <HI produz seis bytes: dois para o campo H e quatro para o campo I, todos little-endian. Passe exatamente o número de valores que o formato espera – uma incompatibilidade lança struct.error.
2.29.4. Desempacotamento¶
width, count = struct.unpack("<HI", blob)
print(width, count)
Saída:
320 1000000
struct.unpack() sempre retorna uma tupla, mesmo quando o formato descreve um único campo. Desempacote-a na mesma linha para melhor legibilidade.
2.29.5. Strings de bytes de tamanho fixo¶
O código s lê ou escreve um trecho de bytes literalmente. A contagem vem antes do s – 4s significa “quatro bytes tratados como uma única string de bytes”. Essa é a forma usual de embutir um valor mágico, uma tag de tamanho fixo ou um campo de nome com preenchimento em um registro:
header = struct.pack("<4sHI", b"OMV0", 320, 1000000)
print(header)
Saída:
b'OMV0@\x01@B\x0f\x00'
Os quatro primeiros bytes são o valor mágico literal b"OMV0"; os dois seguintes são o campo H (320); os últimos quatro são o campo I (1000000). O desempacotamento retorna os bytes de volta como um objeto bytes:
magic, width, count = struct.unpack("<4sHI", header)
print(magic, width, count)
Saída:
b'OMV0' 320 1000000
Se o valor de origem for mais curto que a contagem declarada, o resultado é preenchido à direita com \x00; se for mais longo, os bytes excedentes são descartados silenciosamente:
struct.pack("4s", b"hi") # b'hi\x00\x00'
struct.pack("4s", b"toolong") # b'tool'
A contagem é um comprimento em bytes, não uma contagem de caracteres – s lida com bytes brutos, então uma string UTF-8 com caracteres de múltiplos bytes precisa primeiro ser passada por .encode() e contada em bytes.
2.29.6. Dimensionamento e leituras parciais¶
struct.calcsize() retorna o número de bytes que uma string de formato consome:
struct.calcsize("<HI") # 6
Ao ler um fluxo de registros de um arquivo, leia exatamente essa quantidade de bytes por registro:
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)
Uma leitura curta no fim do arquivo produz um trecho menor que record_size – trate isso como a condição de fim de fluxo, em vez de tentar desempacotar um registro parcial.