El módulo struct en Python convierte enteros, floats, booleanos y secuencias de bytes en estructuras binarias compactas. También realiza la operación inversa: interpreta bytes recibidos desde archivos, dispositivos o conexiones de red según una cadena de formato.
Es útil para protocolos, cabeceras de archivos, integración con C, sensores y formatos antiguos. El principal riesgo es depender de la plataforma. Sin prefijo, struct usa orden de bytes, tamaños C y alineación nativos. Para intercambiar datos, define explícitamente endianness y tamaños.
Empaqueta y desempaqueta
import struct
formato = ">Ih"
paquete = struct.pack(formato, 1_000_000, -12)
identificador, temperatura = struct.unpack(formato, paquete)
print(paquete.hex())
print(identificador, temperatura)> selecciona big-endian con tamaños estándar. I es un entero sin signo de 32 bits y h uno con signo de 16 bits. unpack() siempre devuelve una tupla.
Prefijos de orden de bytes
<: little-endian, tamaños estándar y sin alineación implícita.>: big-endian con tamaños estándar.!: orden de red, equivalente a big-endian.=: orden nativo con tamaños estándar.@: orden, tamaño y alineación completamente nativos.
import struct
print(struct.pack(">H", 1023).hex()) # 03ff
print(struct.pack("<H", 1023).hex()) # ff03Para archivos y protocolos, evita el modo nativo implícito. Elige <, > o ! para obtener el mismo layout en todas las arquitecturas.
Calcula el tamaño necesario
import struct
CABECERA = struct.Struct("!4sBBHI")
print(CABECERA.size)
with open("mensaje.bin", "rb") as archivo:
bruto = archivo.read(CABECERA.size)
if len(bruto) != CABECERA.size:
raise ValueError("cabecera incompleta")
magia, version, flags, tipo, longitud = CABECERA.unpack(bruto)unpack() necesita un buffer de tamaño exacto. unpack_from() requiere al menos el tamaño del formato después del offset. Valida antes de interpretar.
Reutiliza un Struct compilado
import struct
REGISTRO = struct.Struct("<Iff?")
payload = REGISTRO.pack(42, 18.5, 70.25, True)
identificador, x, y, activo = REGISTRO.unpack(payload)El módulo mantiene una caché de formatos recientes, pero los objetos con nombre documentan mejor el protocolo y reducen repetición.
Caracteres frecuentes
b/B: entero de 8 bits con o sin signo.h/H: entero de 16 bits.i/I: entero estándar de 32 bits.q/Q: entero de 64 bits.e,f,d: float de 16, 32 y 64 bits.?: booleano.s: bytes de longitud fija.x: byte de padding.
Python 3.14 añadió F y D para números complejos de precisión simple y doble.
Bytes de longitud fija
import struct
FORMATO = struct.Struct("!10sI")
bruto = FORMATO.pack(b"sensor-1", 123)
nombre, lectura = FORMATO.unpack(bruto)
nombre = nombre.rstrip(b"\x00").decode("ascii")10s es un único campo de diez bytes. Una entrada mayor se trunca y una menor se rellena con ceros. Valida la longitud si el truncamiento sería una pérdida de datos.
Escribe en buffers existentes
import struct
buffer = bytearray(1024)
CABECERA = struct.Struct("!IHH")
CABECERA.pack_into(buffer, 0, 900, 2, 7)
identificador, version, flags = CABECERA.unpack_from(buffer, 0)Estos métodos aceptan objetos del protocolo de buffer, como bytearray y memoryview, reduciendo asignaciones temporales.
Interpreta registros repetidos
import struct
REGISTRO = struct.Struct("<Ih")
datos = b"".join([
REGISTRO.pack(1, 20),
REGISTRO.pack(2, 25),
REGISTRO.pack(3, 18),
])
for identificador, valor in REGISTRO.iter_unpack(datos):
print(identificador, valor)La longitud total debe ser múltiplo del tamaño del registro. Rechaza bytes sobrantes porque pueden indicar corrupción o una versión distinta.
Protocolos con longitud
import struct
HEADER = struct.Struct("!4sBI")
MAX_PAYLOAD = 10 * 1024 * 1024
cabecera = recibir_exactamente(HEADER.size)
magia, version, longitud = HEADER.unpack(cabecera)
if magia != b"APP1" or version != 1:
raise ValueError("protocolo no soportado")
if longitud > MAX_PAYLOAD:
raise ValueError("payload demasiado grande")
payload = recibir_exactamente(longitud)Nunca reserves memoria únicamente con una longitud enviada por el cliente. Aplica máximo, timeout, rate limit y límite de mensajes.
Red y cuerpos comprimidos
Usa ! para orden de red. El cuerpo puede comprimirse con zlib en Python, pero la cabecera debe identificar versión, flags, algoritmo y límites. Los valores internos descritos en opcode en Python no son identificadores estables de protocolo.
Alineación nativa
Con @, el compilador C puede insertar padding. Es útil para reflejar una struct C en el mismo entorno, pero no para almacenamiento portátil.
import struct
print(struct.calcsize("@ci"))
print(struct.calcsize("@ic"))
print(struct.calcsize("=ci"))El orden de los campos puede cambiar el tamaño. En modos estándar, el padding solo aparece si se declara con x.
Rangos y struct.error
import struct
try:
struct.pack("!h", 100_000)
except struct.error as error:
raise ValueError("el valor no cabe en 16 bits con signo") from errorValida también valores reservados, combinaciones de flags, NaN, infinito y rangos de negocio.
No es un serializador universal
struct no guarda nombres, evolución de esquema ni tipos semánticos. Es excelente para layouts compactos y estables. JSON o formatos con esquema son mejores para objetos flexibles.
Seguridad
- Valida la longitud del buffer.
- Define endianness.
- Limita longitudes declaradas.
- Rechaza versiones desconocidas.
- Usa timeouts.
- Define bytes reservados y padding.
- No uses
Pcon datos externos. - Haz fuzzing del parser.
Pruebas
Prueba valores mínimos y máximos, endian invertido, buffers cortos, bytes extra, registros incompletos, NaN, infinito, NUL, versiones futuras y payloads superiores al máximo. Verifica bytes exactos, no solo round trips.
def test_header_bytes():
assert HEADER.pack(b"APP1", 1, 5) == b"APP1\x01\x00\x00\x00\x05"Buenas prácticas
- Crea constantes
Structcon nombre. - Documenta campos y unidades.
- Incluye magia y versión.
- Usa tamaños estándar.
- Valida antes de reservar.
- Usa
pack_intoymemoryview. - No vuelques binarios sensibles en logs.
Conclusión
struct en Python conecta valores Python con layouts binarios eficientes. Funciona muy bien para cabeceras, archivos y protocolos cuando orden, tamaño, versión y límites son explícitos.
Consulta la documentación oficial de struct y la documentación del protocolo de buffer. Para límites por entorno, revisa configparser en Python, y para seguir la ejecución usa trace en Python.







