El formato pickle representa objetos Python como una secuencia de instrucciones. Cargar un archivo con pickle.load() ejecuta esas instrucciones y puede importar funciones o llamar constructores, por lo que los datos no confiables implican riesgo de ejecución de código. El módulo pickletools en Python desmonta el flujo en opcodes legibles sin ejecutar el pickle, ayudando a estudiar protocolos, investigar archivos y optimizar serializaciones.
Esta guía explica la interfaz de terminal, dis(), genops() y optimize(), además de sus límites de seguridad. Complementa nuestros artículos sobre pickle en Python, bytecode con dis, shelve, tracebacks y comparación de archivos.
Por qué pickle exige cuidado
Un pickle no es un contenedor pasivo de datos. Su flujo contiene operaciones para reconstruir objetos y puede referenciar globals, callables y mecanismos de reducción.
import pickle
with open("datos.pickle", "rb") as archivo:
objeto = pickle.load(archivo)Este código es seguro únicamente cuando el archivo procede de una fuente totalmente confiable y su integridad está protegida. Nunca cargues un upload, adjunto o archivo descargado al azar.
El papel de pickletools
Pickletools interpreta la estructura del formato y muestra los opcodes sin ejecutar la reconstrucción.
La documentación oficial de pickletools indica que el módulo resulta especialmente útil para desarrolladores de pickle y del núcleo de Python, aunque también sirve para auditoría y aprendizaje.
Crear un pickle de prueba
import pickle
contenido = {
"nombre": "Ana",
"puntos": [10, 20, 30],
}
datos = pickle.dumps(
contenido,
protocol=pickle.HIGHEST_PROTOCOL,
)
with open("ejemplo.pickle", "wb") as archivo:
archivo.write(datos)Usa datos generados localmente para aprender. No llames a pickle.loads() solo para descubrir el contenido de un archivo desconocido.
Desmontar desde la terminal
python -m pickletools ejemplo.pickleLa salida muestra posición, byte del opcode, nombre, argumento e información adicional. Al final informa el protocolo más alto requerido por las instrucciones encontradas.
pickletools frente a python -m pickle
python -m pickle carga el objeto para mostrar su representación. Esto ejecuta el flujo y no debe utilizarse con archivos no confiables.
python -m pickletools desmonta el formato y es la opción más segura para inspección estructural. Aun así, aplica límites de recursos a entradas hostiles.
Anotar cada opcode
La opción -a añade una descripción corta a cada línea.
python -m pickletools -a ejemplo.pickleLas anotaciones ayudan a comprender operaciones como PROTO, FRAME, MARK, MEMOIZE, BINUNICODE y STOP.
Guardar la desmontagem
python -m pickletools \
-o reporte.txt \
ejemplo.pickleNo consideres el reporte textual datos sanitizados. Puede incluir strings, nombres y valores sensibles presentes en el flujo original.
Controlar la indentación
-l define la cantidad de espacios usada por cada nivel iniciado con MARK.
python -m pickletools -l 2 ejemplo.pickleLa indentación es únicamente visual y no modifica el pickle.
Varios archivos y preámbulo
La opción -p imprime un texto antes de cada archivo.
python -m pickletools \
-p '=== siguiente pickle ===' \
a.pickle b.pickleEsto facilita leer reportes concatenados.
Conservar memo entre archivos
-m mantiene el memo al desmontar varios flujos.
python -m pickletools -m parte1.pickle parte2.pickleÚsalo solo cuando los archivos fueron producidos por un proceso compatible que comparte memo. Los archivos independientes deben analizarse por separado.
Leer desde stdin
cat ejemplo.pickle | python -m pickletools -No mezcles datos binarios y mensajes de log en el mismo stream. Impone un tamaño máximo antes de enviar uploads.
Usar dis() programáticamente
pickletools.dis() escribe una desmontagem simbólica en un objeto de archivo.
import io
import pickletools
salida = io.StringIO()
pickletools.dis(
datos,
out=salida,
annotate=40,
)
print(salida.getvalue())El argumento pickle puede ser bytes o un objeto file-like. La salida predeterminada es sys.stdout.
Memo programático
El parámetro memo recibe un diccionario compartido entre desmontajes.
memo = {}
pickletools.dis(datos_a, memo=memo)
pickletools.dis(datos_b, memo=memo)El memo representa objetos guardados por el protocolo. Compartirlo entre flujos no relacionados puede producir interpretaciones incorrectas.
Iterar opcodes con genops()
genops() produce triples (opcode, argumento, posición).
for opcode, argumento, posicion in pickletools.genops(datos):
print(
posicion,
opcode.name,
argumento,
)Cada OpcodeInfo contiene nombre, código, documentación, efecto de pila, protocolo mínimo y otros metadatos de bajo nivel.
Crear un reporte estructurado
def reporte_pickle(datos: bytes):
for opcode, argumento, posicion in pickletools.genops(datos):
yield {
"posicion": posicion,
"opcode": opcode.name,
"protocolo": opcode.proto,
"argumento": repr(argumento),
}Limita la longitud de repr(argumento) porque strings y blobs pueden ser enormes.
Señalar operaciones sensibles
Una auditoría puede destacar opcodes relacionados con globals y llamadas de reconstrucción.
ALERTAS = {
"GLOBAL",
"STACK_GLOBAL",
"REDUCE",
"BUILD",
"OBJ",
"INST",
"NEWOBJ",
"NEWOBJ_EX",
}
for opcode, argumento, posicion in pickletools.genops(datos):
if opcode.name in ALERTAS:
print("Atención:", posicion, opcode.name, argumento)Una allowlist o blocklist de opcodes no hace seguro el unpickle. Combinaciones, extensiones y objetos aparentemente permitidos todavía pueden ser peligrosos.
Protocolos
Pickle posee varias versiones de protocolo. Los protocolos modernos pueden incorporar frames, memoización más eficiente y mejor soporte para objetos grandes.
import pickle
for protocolo in range(pickle.HIGHEST_PROTOCOL + 1):
datos = pickle.dumps({"x": 1}, protocol=protocolo)
nombres = [op.name for op, _, _ in pickletools.genops(datos)]
print(protocolo, nombres)El protocolo no equivale a la versión de Python, aunque el intérprete determina cuáles están disponibles.
PROTO y protocolo más alto
El opcode PROTO declara una versión, pero la desmontagem también calcula el protocolo más alto exigido por las operaciones. Los flujos antiguos pueden no comenzar con PROTO.
Las herramientas deben analizar la secuencia completa.
Frames
Los protocolos modernos pueden dividir el flujo con FRAME. Los frames ayudan al unpickler a procesar bloques y reducir llamadas de lectura.
Un tamaño declarado no debe provocar una asignación ilimitada. Procesa entradas desconocidas con límites de memoria y tamaño.
El memo
El memo evita serializar repetidamente el mismo objeto y conserva referencias compartidas.
lista = []
objeto = [lista, lista]
datos = pickle.dumps(objeto, protocol=4)
pickletools.dis(datos)La salida muestra almacenamiento y recuperación de la referencia, explicando por qué ambos elementos apuntan a la misma lista tras el unpickle.
Optimizar con optimize()
pickletools.optimize() elimina opcodes PUT no utilizados y devuelve un flujo equivalente.
optimizado = pickletools.optimize(datos)
print(len(datos), len(optimizado))Puede ocupar menos espacio, transmitirse más rápido y cargarse con mayor eficiencia. Optimize no es un sanitizador.
Verificar solamente datos confiables
original = pickle.loads(datos)
objeto_optimizado = pickle.loads(optimizado)
assert original == objeto_optimizadoEste test ejecuta pickle y debe hacerse únicamente con datos generados o autenticados por el propio sistema.
Autenticar pickles internos
Cuando una aplicación realmente necesita pickle, protege integridad y autenticidad con HMAC o firma y conserva la clave por separado.
Una firma detecta alteraciones, pero no vuelve confiable contenido de terceros. Solo prueba que un poseedor de la clave aprobó el flujo.
Preferir formatos de datos en fronteras
Para entrada externa e interoperabilidad, elige JSON, MessagePack, Protocol Buffers o un esquema de base de datos. Estos formatos representan datos y no instrucciones arbitrarias de reconstrucción Python.
Pickle resulta más adecuado para comunicación y persistencia internas entre componentes confiables y compatibles.
Límites de recursos
Incluso sin ejecutar opcodes, el análisis puede consumir recursos mediante archivos enormes, argumentos extensos o secuencias artificiales.
Define tamaño máximo, timeout, cantidad de opcodes y salida truncada. Analiza uploads en un subprocess con CPU y memoria limitadas.
Scanner aislado
from pathlib import Path
def analizar(ruta: Path, limite=10_000_000):
if ruta.stat().st_size > limite:
raise ValueError("Archivo demasiado grande")
with ruta.open("rb") as archivo:
for indice, (op, arg, pos) in enumerate(
pickletools.genops(archivo)
):
if indice > 100_000:
raise ValueError("Demasiados opcodes")
yield pos, op.name, argAcepta solo archivos regulares dentro de una raíz autorizada y evita symlinks inesperados.
pickletools no certifica seguridad
La desmontagem ayuda a comprender un flujo, pero no prueba que sea inofensivo. El comportamiento final depende de objetos importados, reducers, extensiones y código disponible.
La regla permanece: nunca hagas unpickle de datos no confiables.
Errores frecuentes
- Usar
python -m picklecon un archivo desconocido. - Cargar el objeto después de desmontar para confirmar contenido.
- Confiar en una pequeña blocklist.
- Compartir memo entre archivos independientes.
- Generar salida sin limitar argumentos.
- Tratar optimize como sanitización.
- Ignorar límites de tamaño y opcodes.
- Suponer compatibilidad permanente entre versiones.
Buenas prácticas
- Usa pickletools para inspección sin ejecución.
- Mantén archivos desconocidos lejos de pickle.load.
- Limita tamaño, tiempo, opcodes y salida.
- Analiza archivos hostiles en subprocess aislado.
- Usa formatos declarativos en interfaces externas.
- Autentica solo pickles generados internamente.
- Registra protocolo y versión.
- Prueba optimize solo con datos confiables.
Conclusión
El módulo pickletools en Python revela las instrucciones de un pickle sin ejecutar su bytecode. La terminal y dis() generan desmontajes legibles, genops() permite análisis estructurado y optimize() elimina memoizaciones innecesarias.
Esta visibilidad ayuda a aprender el formato e investigar archivos, pero no hace seguro pickle. La protección real consiste en no cargar entradas desconocidas, imponer límites y preferir formatos declarativos en las fronteras del sistema.







