2.29. Struct e dados binários¶
O módulo struct empacota valores Python numa disposição binária fixa e desempacota bytes de volta em valores Python. Recorra a ele quando trabalhar com um formato de ficheiro binário, um protocolo de rede, ou um dispositivo que troca registos de tamanho fixo.
Duas funções cobrem a maioria dos casos:
struct.pack()– recebe valores Python e uma string de formato, e devolve um objetobytescom a disposição exata.struct.unpack()– recebe uma string de formato e um objetobytes, e devolve um tuple de valores Python.
2.29.1. Strings de formato¶
Uma string de formato lista um código por campo no registo. Os códigos descrevem tanto o tamanho como a interpretação de cada campo.
O int do Python não tem tamanho fixo – cresce para conter qualquer valor que lhe seja atribuído. Os formatos binários têm tamanhos fixos: cada campo inteiro utiliza um número acordado de bytes. O struct converte entre inteiros Python de tamanho ilimitado e estas representações de tamanho fixo.
A largura de um inteiro é o número de bits que utiliza. Um byte corresponde a oito bits. O código em minúsculas é a variante com sinal; o código em maiúsculas é o 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 mil milhões com sinal, quatro mil milhões sem sinal.q/Q– 64 bits (oito bytes). Efetivamente ilimitado para uso corrente.
Escolha uma largura que cubra confortavelmente o intervalo esperado. Empacotar um valor fora do intervalo declarado pode resultar em truncagem silenciosa ou levantar struct.error, consoante a versão.
Os restantes códigos comuns destinam-se a números de vírgula flutuante e strings de bytes:
f– vírgula flutuante de 32 bits (precisão simples; cerca de sete dígitos decimais). Ofloatnormal do MicroPython já tem este tamanho, pelo que empacotá-lo emfnão provoca perda de precisão.d– vírgula flutuante de 64 bits (precisão dupla; cerca de quinze dígitos decimais). Empacotar umfloatMicroPython de 32 bits emdalarga-o para oito bytes, mas não acrescenta precisão.s– string de bytes de comprimento fixo, precedida de um contador (8spara um campo de oito bytes).
2.29.2. Ordem dos bytes¶
Um inteiro multi-byte pode ser armazenado em memória de duas formas. O número 0x12345678 num campo de 32 bits é disposto assim:
Little-endian – byte menos significativo primeiro:
78 56 34 12.Big-endian – byte mais significativo primeiro:
12 34 56 78.
Ambos codificam o mesmo valor; apenas diferem no extremo do campo que contém o byte de menor peso. Um ficheiro escrito por um sistema fica corrompido ao ser lido pelo outro se a ordem dos bytes não coincidir.
O caracter inicial da string de formato define a ordem:
<– little-endian. Comum em x86 e ARM.>– big-endian. Comum em protocolos de rede.!– ordem de rede, equivalente a>.
Sem um caracter inicial, é utilizada a ordem e o alinhamento nativos; definir < ou > explicitamente elimina essa ambiguidade e é geralmente o pretendido ao ler um ficheiro ou comunicar com outra máquina.
Nota
A OpenMV Cam é little-endian – tal como o PC anfitrião. Use < nas strings de formato para ficheiros locais da câmara e para dados binários que circulam para ou a partir do PC. 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))
Resultado:
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 discrepância levanta struct.error.
2.29.4. Desempacotamento¶
width, count = struct.unpack("<HI", blob)
print(width, count)
Resultado:
320 1000000
struct.unpack() devolve sempre um tuple, mesmo quando o formato descreve um único campo. Desempacote-o na mesma linha para maior legibilidade.
2.29.5. Strings de bytes de comprimento fixo¶
O código s lê ou escreve um bloco de bytes literalmente. O contador vai antes do s – 4s significa «quatro bytes tratados como uma única string de bytes». É a forma habitual de incorporar um valor mágico, uma etiqueta de tamanho fixo, ou um campo de nome com preenchimento num registo:
header = struct.pack("<4sHI", b"OMV0", 320, 1000000)
print(header)
Resultado:
b'OMV0@\x01@B\x0f\x00'
Os primeiros quatro 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 devolve os bytes como um objeto bytes:
magic, width, count = struct.unpack("<4sHI", header)
print(magic, width, count)
Resultado:
b'OMV0' 320 1000000
Se o valor de origem for mais curto do que o contador declarado, o resultado é preenchido à direita com \x00; se for mais longo, os bytes a mais são silenciosamente descartados:
struct.pack("4s", b"hi") # b'hi\x00\x00'
struct.pack("4s", b"toolong") # b'tool'
O contador é um comprimento em bytes, não em caracteres – s trabalha com bytes brutos, pelo que uma string UTF-8 com caracteres multi-byte precisa de ser .encode()“d e o comprimento contado em bytes primeiro.
2.29.6. Dimensionamento e leituras parciais¶
struct.calcsize() devolve o número de bytes que uma string de formato consome:
struct.calcsize("<HI") # 6
Ao ler um fluxo de registos de um ficheiro, leia exatamente esse número de bytes por registo:
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 final do ficheiro produz um bloco menor que record_size – trate isso como a condição de fim de fluxo em vez de tentar desempacotar um registo parcial.