El módulo marshal lee y escribe un formato binario interno utilizado principalmente por Python para almacenar code objects en archivos compilados. Puede representar varios tipos básicos, como números, strings, bytes, tuplas, listas, sets, frozensets, diccionarios y objetos de código, según la versión del intérprete.
Aunque puede parecer una alternativa rápida a JSON o pickle, marshal no fue diseñado como formato general de persistencia. La compatibilidad no está garantizada para todos los tipos, los detalles internos pueden cambiar y nunca deben cargarse datos inválidos o maliciosos. Sus usos apropiados son limitados: tooling de Python, caches descartables, experimentos controlados y estudio de internals.
marshal y archivos .pyc
Cuando Python compila un módulo, puede crear un archivo de bytecode dentro de __pycache__. El archivo incluye un header del sistema de imports y una representación de un code object.
No trates un archivo .pyc completo como un simple resultado de marshal.dumps(). El header contiene metadatos y cambia con el tiempo. Prefiere utilidades de import y herramientas especializadas.
Valores básicos
El formato admite un conjunto seleccionado de tipos nativos.
import marshal
valor = {
"nombre": "ejemplo",
"version": 1,
"items": [1, 2, 3],
}
datos = marshal.dumps(valor)
restaurado = marshal.loads(datos)
print(restaurado)
Un round trip exitoso conserva valores soportados, pero no crea un contrato público estable.
dump y load
dump(value, file) escribe en un stream binario y load(file) lee un objeto.
import marshal
with open("estado.bin", "wb") as archivo:
marshal.dump(valor, archivo)
with open("estado.bin", "rb") as archivo:
valor = marshal.load(archivo)
Abre siempre en modo binario. Un stream de texto intenta convertir caracteres y no admite bytes arbitrarios.
dumps y loads
dumps() devuelve bytes y loads() acepta un valor bytes-like.
payload = marshal.dumps((1, 2, 3))
valor = marshal.loads(payload)
Es práctico cuando el almacenamiento, hashing o transporte ya trabaja con bytes.
Versiones del formato
Las funciones de escritura aceptan una versión. El default corresponde al formato recomendado por el intérprete actual.
payload = marshal.dumps(valor, 4)
No selecciones una versión solo porque su número sea mayor. Consulta la documentación del runtime y prueba el destino.
Compatibilidad limitada
Python conserva determinados valores simples entre cambios cuando es posible, pero los code objects no son compatibles entre versiones de Python. Los internals cambian.
Usa un formato documentado y versionado si los datos deben sobrevivir upgrades, cruzar servicios o ser leídos por otro lenguaje.
Code objects
Un code object contiene bytecode, constantes, nombres, variables y metadatos de ejecución.
import marshal
codigo = compile("resultado = 2 + 3", "ejemplo.py", "exec")
payload = marshal.dumps(codigo)
restaurado = marshal.loads(payload)
namespace = {}
exec(restaurado, namespace)
print(namespace["resultado"])
Ejecutar el objeto restaurado ejecuta código. Hazlo solo con datos producidos y protegidos por un sistema confiable.
allow_code
Las APIs actuales ofrecen el parámetro allow_code para controlar si pueden serializarse o deserializarse code objects.
datos = marshal.dumps(valor, allow_code=False)
restaurado = marshal.loads(datos, allow_code=False)
Deshabilitar code objects elimina una categoría de riesgo, pero no vuelve segura la entrada hostil.
Entrada no confiable
La documentación oficial advierte contra cargar datos de fuentes no confiables. Un payload malformado puede provocar fallos, consumo excesivo u otros comportamientos inseguros.
Nunca llames marshal.loads() con requests HTTP, mensajes de queues públicas, uploads o caches compartidos sin una frontera fuerte de confianza.
Protección de integridad
Cuando un archivo interno es importante, protégelo con permisos y, cuando corresponda, MAC o firma criptográfica. La verificación ayuda a detectar alteraciones.
Una firma no resuelve incompatibilidad y solo funciona si clave y productor son confiables.
Limita el tamaño
Comprueba el tamaño antes de leer. Las estructuras grandes consumen memoria y CPU.
from pathlib import Path
ruta = Path("estado.bin")
if ruta.stat().st_size > 10_000_000:
raise ValueError("archivo demasiado grande")
El límite debe reflejar el caso real.
Profundidad y recursión
Valores muy anidados pueden superar límites internos. No aumentes la recursión solo para aceptar payloads arbitrarios.
Valida la estructura lógica después de la carga y prefiere schemas poco profundos.
Objetos no soportados
Instancias de clases propias, funciones normales, conexiones activas, generators y muchos otros objetos no tienen representación directa.
try:
marshal.dumps(object())
except ValueError as error:
print(error)
No construyas una capa compleja solo para forzar el formato. JSON, dataclasses o pickle controlado pueden representar mejor el dominio.
Valores singleton
None, True y False son soportados. Aun así, valida tipo y schema al cargar.
Que un valor pueda representarse no significa que sea válido para la aplicación.
Diccionarios
Los mappings pueden contener claves y valores soportados. El orden observable no debe tratarse como contrato del formato.
La configuración se expresa mejor con un schema explícito y versionado.
Sets
Sets y frozensets pueden representarse según la versión del formato. Como no tienen orden semántico, los bytes no sirven como hash canónico.
Normaliza la estructura según una especificación propia cuando necesites firma determinística.
Números float y complex
El formato admite tipos numéricos de Python, pero no busca interoperabilidad.
Para decimales precisos o dinero portable, serializa strings o enteros bajo un schema explícito.
marshal frente a pickle
Pickle soporta más objetos y customización mediante herramientas como copyreg en Python. Ambos son específicos de Python y no deben recibir entrada no confiable.
marshal es más restringido y está más ligado al intérprete.
marshal frente a JSON
JSON tiene menos tipos, pero es documentado, interoperable y apropiado para APIs con validación.
Usa JSON para datos de aplicación y comunicación. Usa marshal solo cuando los internals de Python sean una necesidad real.
marshal frente a struct
struct empaqueta valores según un layout binario explícito y es mejor para protocolos y archivos estables.
Marshal describe valores Python y no publica un layout pensado para otras implementaciones.
Caches descartables
Un cache creado y consumido por la misma versión puede ser aceptable. El sistema debe poder borrarlo y reconstruirlo tras cualquier error.
Nunca hagas que los datos marshal sean la única copia importante.
Metadatos externos
Guarda junto al payload versión de la aplicación, versión de Python, checksum y timestamp.
metadatos = {
"python": platform.python_version(),
"schema": 1,
}
Así puedes rechazar caches incompatibles antes de la carga.
Escritura atómica
Escribe primero en un archivo temporal, realiza flush si importa la durabilidad y sustituye con os.replace().
Un crash durante escritura directa puede dejar un payload truncado.
Acceso concurrente
Varios procesos no deben sobrescribir el mismo archivo sin coordinación. Usa locks, nombres versionados o un servicio de cache.
La sustitución atómica permite que lectores vean la versión anterior o la nueva.
Fallos de lectura
Maneja errores esperados, descarta caches reconstruibles y conserva diagnóstico.
try:
valor = marshal.loads(payload)
except (EOFError, ValueError, TypeError) as error:
registrar_cache_invalido(error)
valor = reconstruir()
No continúes con estado parcialmente leído.
Auditoría
Las operaciones pueden emitir eventos de auditoría de Python. Entornos controlados pueden observar carga de objetos y code objects.
Los audit hooks complementan permisos y aislamiento.
Análisis de bytecode
Para estudiar code objects, combina compile(), marshal controlado y el módulo dis. Nunca ejecutes bytecode desconocido.
ast en Python explica una capa estructural anterior.
Tests entre versiones
Si un cache cruza deployments, prueba cada versión soportada. Incluye lectura antigua, rechazo de incompatible y reconstrucción automática.
Un round trip en el mismo proceso no es suficiente.
Observabilidad
Registra tamaño, versión de aplicación, versión de Python, duración y motivo de invalidación. No guardes bytes crudos.
Errores comunes
Los fallos frecuentes son usar marshal como base de datos, leer payload externo, asumir compatibilidad de code objects, tratar un .pyc como payload puro, ejecutar código sin confianza, omitir límites y mantener información importante solo en este formato.
Conclusión
marshal es una herramienta interna de Python apropiada para code objects y caches descartables bajo control estricto. Usa streams binarios, valida tamaño y versiones, protege el origen y reconstruye caches incompatibles.
Para datos de aplicación, prefiere formatos estables y schemas explícitos. Consulta la documentación oficial de marshal y la documentación de dis.







