El módulo struct convierte valores Python en secuencias de bytes y reconstruye valores desde buffers binarios. Se utiliza en protocolos de red, headers de archivos, dispositivos, bases binarias, memoria compartida e integración con programas C. Una string de formato describe tipos, tamaños, alineación y orden de bytes.
Usar struct exige precisión. Un formato incorrecto puede interpretar bytes válidos como valores absurdos, truncar información o provocar asignaciones excesivas. Define un layout documentado, valida tamaños antes de desempaquetar y nunca confíes en contadores u offsets leídos de archivos o conexiones.
Primer pack y unpack
pack() recibe un formato y valores; unpack() realiza la conversión inversa.
import struct
datos = struct.pack("!HI", 2, 4096)
version, longitud = struct.unpack("!HI", datos)
print(version, longitud)
El prefijo ! selecciona orden de red, equivalente a big-endian con tamaños estandarizados.
Caracteres de formato
Los códigos frecuentes incluyen b y B para enteros de 8 bits, h/H para 16 bits, i/I para enteros, q/Q para 64 bits, f y d para floating point, ? para booleanos, s para bytes de tamaño fijo y x para padding.
Documenta el significado de cada campo fuera de la string. Un layout compacto sin nombres es difícil de revisar.
Endianness
Los prefijos controlan orden y alineación: > es big-endian, < little-endian, ! orden de red, = orden nativo con tamaños estándar y @ layout nativo completo.
little = struct.pack("<I", 0x12345678)
big = struct.pack(">I", 0x12345678)
print(little.hex(), big.hex())
Los formatos persistentes deben elegir un orden explícito. No uses @ para datos intercambiados entre arquitecturas.
Calcula el tamaño
calcsize() informa cuántos bytes ocupa un formato.
FORMATO = "!4sBHI"
TAMANO = struct.calcsize(FORMATO)
Usa este valor para validar buffers y calcular offsets sin números mágicos.
Bytes de tamaño fijo
El especificador Ns representa exactamente N bytes.
header = struct.pack("!4sI", b"DATA", 10)
magic, longitud = struct.unpack("!4sI", header)
Una entrada corta se completa con ceros y una entrada larga se trunca. Valida la longitud cuando el truncamiento silencioso sea peligroso.
Texto y encoding
struct no codifica strings Python. Convierte texto explícitamente.
nombre = "acción".encode("utf-8")
if len(nombre) > 32:
raise ValueError("nombre demasiado largo")
bloque = struct.pack("!32s", nombre)
Al decodificar, elimina padding únicamente según el protocolo.
unpack_from
unpack_from() lee campos desde un buffer existente a partir de un offset sin crear un slice.
version, flags = struct.unpack_from("!BB", buffer, 4)
Comprueba que offset + calcsize(formato) quepa en el buffer.
pack_into
pack_into() escribe en un buffer mutable como bytearray, memoryview o mmap.
buffer = bytearray(16)
struct.pack_into("!I", buffer, 0, 123)
Esto reduce asignaciones en loops, pero exige control riguroso de offsets y concurrencia.
iter_unpack
iter_unpack() recorre registros de tamaño fijo.
FORMATO = "!Ih"
for identificador, valor in struct.iter_unpack(FORMATO, datos):
procesar(identificador, valor)
La longitud total debe ser múltiplo del tamaño del registro.
Registros variables
Para payloads variables, utiliza un header fijo que declare la longitud del cuerpo.
HEADER = "!I"
longitud = struct.unpack(HEADER, recibir_exacto(4))[0]
if longitud > 1_000_000:
raise ValueError("payload demasiado grande")
payload = recibir_exacto(longitud)
Aplica el límite antes de reservar memoria.
Enteros con signo
Los códigos minúsculos suelen representar enteros con signo y los mayúsculos, sin signo. Valores fuera del rango generan struct.error.
También valida el dominio de aplicación. Un valor representable puede ser inválido para el protocolo.
Punto flotante
f representa precisión simple y d, doble. Los resultados pueden contener redondeo, infinito y NaN.
No uses floats binarios para dinero o contadores exactos. Prefiere enteros escalados o una representación decimal definida.
Booleanos
El formato ? serializa booleanos. Al desempaquetar, cualquier byte distinto de cero se interpreta como verdadero.
Si el protocolo permite únicamente 0 o 1, valida el byte o usa B.
Padding y alineación
El modo nativo @ puede insertar padding para reproducir una estructura C local. El layout cambia según arquitectura y ABI.
Para archivos y red, prefiere <, > o !. Usa layout nativo solo para integración local probada en cada plataforma.
Integración con mmap
unpack_from() puede leer directamente de un archivo mapeado.
import mmap
with mmap.mmap(archivo.fileno(), 0, access=mmap.ACCESS_READ) as mapa:
magic, version = struct.unpack_from("!4sH", mapa, 0)
Consulta mmap en Python para lifecycle, alineación y sincronización.
Integración con sockets
TCP es un flujo; una llamada a recv() puede devolver menos bytes de los necesarios. Recibe exactamente el header antes de desempaquetar.
Consulta socket en Python para framing y timeouts.
Clase Struct
Cuando reutilices un formato, compílalo con struct.Struct.
HEADER = struct.Struct("!4sBHI")
bloque = HEADER.pack(b"DATA", 1, 0, 128)
campos = HEADER.unpack(bloque)
Esto centraliza el layout y puede reducir overhead.
Layouts versionados
Incluye magic bytes y versión al principio. El parser puede seleccionar un layout conocido y rechazar versiones desconocidas.
Añade campos de forma compatible o crea una versión nueva. No cambies silenciosamente el significado de bytes existentes.
Checksums y autenticidad
Un checksum detecta corrupción accidental, pero no demuestra autenticidad frente a un atacante.
Usa MAC o firma digital cuando necesites origen e integridad, cubriendo header y payload.
Offsets y overflow lógico
Los enteros Python son grandes, pero la aritmética de offsets puede superar el buffer o solicitar asignaciones enormes.
fin = offset + cantidad * tamano_item
if cantidad > LIMITE or fin > len(buffer):
raise ValueError("estructura inválida")
Valida antes de crear slices o colecciones.
Datos no confiables
unpack() no ejecuta código, pero un parser inseguro puede agotar CPU o memoria.
Limita profundidad, contadores, longitudes y tiempo. Usa aislamiento de proceso cuando el riesgo lo justifique.
Excepciones
Errores de formato, tamaño o rango generan struct.error.
try:
campos = struct.unpack(FORMATO, bloque)
except struct.error as error:
raise ValueError("registro binario inválido") from error
No registres payloads binarios completos.
Pruebas
Prueba valores mínimos y máximos, cero, negativos, ambas órdenes, NaN, padding, buffers cortos, datos extra, versiones desconocidas y archivos truncados.
Usa vectores conocidos generados por otro lenguaje para comprobar interoperabilidad.
Errores comunes
Los fallos frecuentes son usar layout nativo en archivos portables, olvidar calcsize(), truncar strings silenciosamente, desempaquetar tamaños incorrectos, confiar en longitudes externas, usar float para valores exactos y asumir que un recv entrega un registro completo.
Conclusión
struct es el puente entre valores Python y layouts binarios compactos. Elige endianness explícita, centraliza formatos, valida buffers y versiona protocolos persistentes.
Consulta la documentación oficial de struct, mmap en Python y socket en Python.







