O módulo struct no Python converte valores como inteiros, floats, booleanos e sequências de bytes para layouts binários compactos. O caminho inverso também é suportado: você recebe bytes de um arquivo, dispositivo ou conexão de rede e os interpreta conforme uma estrutura definida por uma string de formato.
Essa ferramenta é útil em protocolos, cabeçalhos de arquivos, integração com C, sensores e formatos legados. O maior risco é confiar implicitamente na plataforma. Sem prefixo, struct usa ordem de bytes, tamanhos e alinhamento nativos, que podem variar. Para intercâmbio externo, defina endianness e tamanhos explicitamente.
Primeiro pack e unpack
import struct
formato = ">Ih"
pacote = struct.pack(formato, 1_000_000, -12)
identificador, temperatura = struct.unpack(formato, pacote)
print(pacote.hex())
print(identificador, temperatura)> seleciona big-endian e tamanhos padronizados. I representa um inteiro sem sinal de 32 bits e h um inteiro com sinal de 16 bits. O resultado de unpack() é sempre uma tupla.
Endianness
A ordem de bytes determina como os bytes mais e menos significativos são armazenados:
<: little-endian, tamanhos padrão e sem alinhamento automático.>: big-endian, tamanhos padrão.!: ordem de rede, equivalente a big-endian.=: ordem nativa com tamanhos padrão e sem padding.@: ordem, tamanho e alinhamento nativos.
import struct
print(struct.pack(">H", 1023).hex()) # 03ff
print(struct.pack("<H", 1023).hex()) # ff03Em protocolos e arquivos, evite o modo nativo. Defina <, > ou ! para que o layout seja igual em todas as máquinas.
Calcule o tamanho antes de ler
calcsize() informa quantos bytes o formato exige:
import struct
CABECALHO = struct.Struct("!4sBBHI")
print(CABECALHO.size)
with open("mensagem.bin", "rb") as arquivo:
bruto = arquivo.read(CABECALHO.size)
if len(bruto) != CABECALHO.size:
raise ValueError("cabeçalho incompleto")
magia, versao, flags, tipo, tamanho = CABECALHO.unpack(bruto)Valide comprimento antes de desempacotar. unpack() exige tamanho exato; unpack_from() precisa de pelo menos o tamanho a partir do offset.
Use Struct para formatos repetidos
A classe Struct compila a string de formato uma vez e reúne tamanho e métodos:
import struct
REGISTRO = struct.Struct("<Iff?")
payload = REGISTRO.pack(42, 18.5, 70.25, True)
identificador, x, y, ativo = REGISTRO.unpack(payload)Os formatos recentes também são armazenados em cache pelas funções do módulo, mas um objeto nomeado documenta o protocolo e reduz repetição.
Caracteres mais usados
b/B: inteiro de 8 bits com/sem sinal.h/H: inteiro de 16 bits.i/I: inteiro padrão de 32 bits.q/Q: inteiro de 64 bits.e,f,d: float de 16, 32 e 64 bits.?: booleano.s: sequência de bytes de tamanho fixo.x: byte de padding.
No Python 3.14, F e D adicionam números complexos de precisão simples e dupla.
Strings de tamanho fixo
import struct
FORMATO = struct.Struct("!10sI")
bruto = FORMATO.pack(b"sensor-1", 123)
nome, leitura = FORMATO.unpack(bruto)
nome = nome.rstrip(b"\x00").decode("ascii")10s é um único campo de dez bytes. Dados maiores são truncados; menores recebem zeros. Valide o comprimento antes de empacotar para não perder conteúdo silenciosamente.
pack_into evita alocação extra
Quando você já possui um bytearray, escreva diretamente:
import struct
buffer = bytearray(1024)
CABECALHO = struct.Struct("!IHH")
CABECALHO.pack_into(buffer, 0, 900, 2, 7)
identificador, versao, flags = CABECALHO.unpack_from(buffer, 0)As funções aceitam objetos do protocolo de buffer, como bytes, bytearray e memoryview. Isso ajuda a reduzir cópias em pipelines binários.
Leia registros repetidos
iter_unpack() interpreta blocos consecutivos do mesmo tamanho:
import struct
REGISTRO = struct.Struct("<Ih")
dados = b"".join([
REGISTRO.pack(1, 20),
REGISTRO.pack(2, 25),
REGISTRO.pack(3, 18),
])
for identificador, valor in REGISTRO.iter_unpack(dados):
print(identificador, valor)O comprimento total deve ser múltiplo do tamanho do registro. Rejeite o bloco se houver sobra, pois ela pode indicar corrupção ou versão diferente.
Protocolo com tamanho de payload
Um cabeçalho frequentemente inclui o comprimento do corpo:
import struct
HEADER = struct.Struct("!4sBI")
MAX_PAYLOAD = 10 * 1024 * 1024
header = receber_exatamente(HEADER.size)
magic, version, length = HEADER.unpack(header)
if magic != b"APP1" or version != 1:
raise ValueError("protocolo inválido")
if length > MAX_PAYLOAD:
raise ValueError("payload excedeu o limite")
payload = receber_exatamente(length)Nunca aloque memória apenas com base em um tamanho fornecido pelo remetente. Aplique teto, timeout e limite de mensagens.
Integração com rede
Use ! para ordem de rede. O artigo sobre zlib no Python mostra como um corpo pode ser comprimido, mas o cabeçalho deve informar versão, flags, algoritmo e tamanho de forma inequívoca. O guia de opcode no Python reforça que números internos da VM não são um protocolo estável; defina seus próprios códigos versionados.
Alinhamento nativo
No modo @, o compilador C da plataforma pode inserir padding entre campos. Isso é útil para espelhar uma struct C dentro do mesmo ambiente, mas perigoso para arquivos portáteis.
import struct
print(struct.calcsize("@ci"))
print(struct.calcsize("@ic"))
print(struct.calcsize("=ci"))A ordem dos campos pode mudar o tamanho no modo nativo. Em formato padrão, padding só aparece quando você o declara com x.
Faixas e struct.error
Valores fora da faixa lançam struct.error:
import struct
try:
struct.pack("!h", 100_000)
except struct.error as erro:
raise ValueError("valor fora de 16 bits") from erroValide também NaN, infinito, valores reservados e combinações de flags conforme o protocolo.
Não use struct como serializador geral
struct não guarda nomes de campos, versões ou esquema. É ideal para layouts compactos e estáveis, não para objetos arbitrários. JSON é melhor para interoperabilidade legível; bancos e formatos como Protocol Buffers oferecem evolução de esquema.
Segurança
- Valide tamanho antes de desempacotar.
- Defina endianness explicitamente.
- Imponha limites a comprimentos declarados.
- Rejeite versões e códigos desconhecidos.
- Use timeouts em rede.
- Não interprete padding ou bytes reservados sem regra.
- Não use ponteiros
Pem dados externos. - Faça fuzzing do parser.
Testes recomendados
Teste limites mínimos e máximos, endian invertido, buffers curtos, bytes extras, registros incompletos, NaN, infinito, caracteres nulos, versões futuras e payloads acima do teto. Compare os bytes esperados, não apenas o round trip.
def test_header_bytes():
assert HEADER.pack(b"APP1", 1, 5) == b"APP1\x01\x00\x00\x00\x05"Boas práticas
- Crie constantes
Structnomeadas. - Documente cada campo e unidade.
- Inclua magia e versão.
- Use tamanhos padrão.
- Valide antes de alocar.
- Use
pack_intoememoryviewquando cópias importarem. - Registre erros sem despejar dados sensíveis.
Conclusão
O struct no Python é uma ponte eficiente entre valores Python e layouts binários. Ele funciona muito bem em cabeçalhos, arquivos e protocolos, desde que ordem de bytes, tamanhos, versões e limites sejam explícitos.
Consulte a documentação oficial do struct e a documentação do protocolo de buffer. Para parametrizar versões e limites, veja configparser no Python, e para observar o parser em execução use trace no Python.







