O módulo struct converte valores Python em sequências de bytes e reconstrói valores a partir de buffers binários. Ele é usado em protocolos de rede, cabeçalhos de arquivos, dispositivos, bancos binários, memória compartilhada e integração com programas escritos em C. A API trabalha com strings de formato que descrevem tipo, tamanho, alinhamento e ordem de bytes.
Usar struct exige precisão. Um formato incorreto pode interpretar bytes válidos como valores absurdos, cortar dados ou abrir espaço para consumo excessivo. Defina um layout documentado, valide tamanhos antes de desempacotar e nunca confie em contagens ou offsets vindos de arquivos e conexões.
O primeiro pack e unpack
pack() recebe um formato e valores; unpack() faz o caminho inverso.
import struct
dados = struct.pack("!HI", 2, 4096)
versao, tamanho = struct.unpack("!HI", dados)
print(versao, tamanho)
O prefixo ! usa ordem de rede, equivalente a big-endian e tamanhos padronizados.
Strings de formato
Caracteres comuns incluem b e B para inteiros de 8 bits, h/H para 16 bits, i/I para inteiros, q/Q para 64 bits, f e d para ponto flutuante, ? para booleano, s para bytes de tamanho fixo e x para padding.
Documente o significado de cada campo fora da string. Um formato compacto sem nomes é difícil de revisar.
Endianness
Os prefixos controlam ordem e alinhamento: > é big-endian, < é little-endian, ! é rede, = usa ordem nativa com tamanhos padrão e @ usa layout nativo completo.
little = struct.pack("<I", 0x12345678)
big = struct.pack(">I", 0x12345678)
print(little.hex(), big.hex())
Protocolos persistentes devem escolher ordem explícita. Não use @ quando dados precisam circular entre arquiteturas.
calcsize
calcsize() informa quantos bytes um formato ocupa.
FORMATO = "!4sBHI"
TAMANHO = struct.calcsize(FORMATO)
Use esse valor para validar buffers, calcular offsets e evitar números mágicos.
Bytes de tamanho fixo
O especificador Ns representa exatamente N bytes.
cabecalho = struct.pack("!4sI", b"DATA", 10)
magic, tamanho = struct.unpack("!4sI", cabecalho)
Se a entrada for menor, pack() completa com zeros; se for maior, corta. Valide comprimento antes de empacotar para não perder informação silenciosamente.
Strings e encoding
struct não codifica texto. Converta strings explicitamente.
nome = "ação".encode("utf-8")
if len(nome) > 32:
raise ValueError("nome grande demais")
bloco = struct.pack("!32s", nome)
Ao decodificar, remova padding apenas conforme o protocolo e use o encoding correto.
unpack_from
unpack_from() lê valores de um buffer a partir de um offset sem criar um slice.
versao, flags = struct.unpack_from("!BB", buffer, 4)
Verifique se offset + calcsize(formato) cabe no buffer.
pack_into
pack_into() grava em um buffer mutável existente, como bytearray, memoryview ou mmap.
buffer = bytearray(16)
struct.pack_into("!I", buffer, 0, 123)
Essa técnica evita alocações repetidas em loops, mas exige controle rigoroso de offsets e concorrência.
iter_unpack
iter_unpack() percorre registros de tamanho fixo.
FORMATO = "!Ih"
for identificador, valor in struct.iter_unpack(FORMATO, dados):
processar(identificador, valor)
O tamanho total do buffer precisa ser múltiplo do tamanho do registro.
Registros com tamanho variável
Para payloads variáveis, use um cabeçalho fixo com comprimento e leia o corpo separadamente.
HEADER = "!I"
tamanho = struct.unpack(HEADER, receber_exato(4))[0]
if tamanho > 1_000_000:
raise ValueError("payload grande demais")
payload = receber_exato(tamanho)
O limite precisa ser aplicado antes da alocação.
Inteiros com sinal
Formatos minúsculos normalmente representam inteiros com sinal; maiúsculos, sem sinal. Valores fora do intervalo geram struct.error.
Valide domínio na camada da aplicação. Um valor tecnicamente representável pode ser inválido para o protocolo.
Ponto flutuante
f representa precisão simples e d, dupla. Resultados podem conter arredondamento, infinito e NaN.
Não use float binário para dinheiro ou contadores exatos. Defina escala inteira ou representação decimal no protocolo.
Booleanos
O formato ? serializa valores booleanos. Ao desempacotar, qualquer byte diferente de zero é verdadeiro conforme a convenção.
Se o protocolo exige apenas 0 ou 1, valide o byte bruto antes ou use B e aplique a regra explicitamente.
Padding e alinhamento
O modo nativo @ pode inserir padding para reproduzir estruturas C. O layout varia conforme arquitetura e compilador.
Para arquivos e rede, prefira <, > ou !. Use layout nativo apenas quando integrar com a ABI local e tiver testes por plataforma.
Integração com mmap
unpack_from() pode ler diretamente de um arquivo mapeado.
import mmap
with mmap.mmap(arquivo.fileno(), 0, access=mmap.ACCESS_READ) as mapa:
magic, versao = struct.unpack_from("!4sH", mapa, 0)
Veja mmap no Python para lifecycle, alinhamento e sincronização.
Integração com sockets
TCP é um fluxo; um recv() pode retornar menos bytes que o necessário. Crie uma função que receba exatamente o cabeçalho antes de chamar unpack().
Consulte socket no Python para framing e timeouts.
Classe Struct
Quando o mesmo formato é usado repetidamente, compile-o com struct.Struct.
CABECALHO = struct.Struct("!4sBHI")
bloco = CABECALHO.pack(b"DATA", 1, 0, 128)
campos = CABECALHO.unpack(bloco)
Isso centraliza o formato e pode reduzir overhead em loops.
Layouts versionados
Inclua magic bytes e versão no início do formato. O parser pode selecionar o layout correto e rejeitar versões desconhecidas.
Adicione campos novos de forma compatível ou crie uma nova versão. Não altere silenciosamente o significado de bytes existentes.
Checksums
Um checksum ajuda a detectar corrupção, mas não fornece autenticidade contra um atacante.
Para segurança, use MAC ou assinatura criptográfica apropriada e cubra cabeçalho e payload.
Offsets e overflow lógico
Python possui inteiros grandes, mas offsets calculados podem ultrapassar o buffer ou gerar alocações enormes.
fim = offset + quantidade * tamanho_item
if quantidade > LIMITE or fim > len(buffer):
raise ValueError("estrutura inválida")
Valide cada operação antes de criar slices ou listas.
Dados não confiáveis
unpack() não executa código, mas um parser inseguro pode consumir CPU e memória ou acessar índices inválidos.
Limite profundidade, contagens, comprimentos e tempo. Faça parsing em processo isolado quando o formato e o risco justificarem.
Exceções
Erros de formato, tamanho ou intervalo geram struct.error.
try:
campos = struct.unpack(FORMATO, bloco)
except struct.error as erro:
raise ValueError("registro binário inválido") from erro
Não exponha dados binários completos em logs.
Testes
Teste valores mínimos e máximos, zero, números negativos, endianness, NaN, padding, buffers curtos, campos extras, versões desconhecidas e arquivos truncados.
Use vetores conhecidos gerados por outra linguagem para confirmar interoperabilidade.
Erros comuns
Os erros mais frequentes são usar layout nativo em arquivo portátil, esquecer calcsize(), truncar strings silenciosamente, chamar unpack() com tamanho errado, confiar em comprimentos externos, usar float para valores exatos e presumir que uma chamada de socket entrega um registro completo.
Conclusão
struct é a ponte entre valores Python e layouts binários compactos. Escolha endianness explícita, centralize formatos, valide buffers e use cabeçalhos versionados.
Consulte a documentação oficial de struct, o guia de mmap no Python e o artigo de socket no Python.







