El módulo dis desensambla code objects de Python y muestra las instrucciones de bytecode ejecutadas por el intérprete. Ayuda a estudiar cómo se compilan expresiones, funciones, loops, comprehensions, excepciones y llamadas. También sirve para enseñanza, debugging de herramientas, análisis de rendimiento y comparación entre versiones.
El bytecode es un detalle de implementación. Instrucciones, argumentos, offsets, caches y optimizaciones pueden cambiar entre releases, incluso menores. No construyas lógica de negocio, formatos persistentes o fronteras de seguridad alrededor de una secuencia exacta sin limitar y probar la versión del runtime.
Desensambla una función
dis.dis() acepta funciones, métodos, clases, módulos, strings de fuente y code objects.
import dis
def sumar(a, b):
return a + b
dis.dis(sumar)
La salida muestra offsets, nombres de instrucciones, argumentos y representaciones útiles.
Compila una expresión
También puedes inspeccionar código compilado dinámicamente.
codigo = compile("resultado = x * 2", "ejemplo.py", "exec")
dis.dis(codigo)
compile() no ejecuta el fuente, pero la entrada no confiable todavía requiere límites.
Code objects
Las funciones exponen __code__ con bytecode, constantes, nombres, variables y metadatos.
code = sumar.__code__
print(code.co_varnames)
print(code.co_consts)
print(code.co_names)
Estos campos son para introspección y pueden evolucionar.
Instrucciones programáticas
dis.get_instructions() devuelve objetos Instruction y es más fiable que parsear texto.
for instruccion in dis.get_instructions(sumar):
print(
instruccion.offset,
instruccion.opname,
instruccion.arg,
instruccion.argval,
)
Usa campos nombrados.
Objeto Instruction
Puede contener nombre del opcode, argumento numérico, valor resuelto, representación, offset, posiciones y marca de jump target.
No todos los campos aplican a todos los opcodes.
Clase Bytecode
dis.Bytecode ofrece una interfaz iterable y helpers de formato.
bytecode = dis.Bytecode(sumar)
for instruccion in bytecode:
print(instruccion.opname)
print(bytecode.dis())
Es útil para herramientas que guardan código y opciones de análisis.
Líneas y posiciones
Las instrucciones pueden asociarse a líneas e intervalos del fuente. Python moderno mantiene posiciones detalladas.
Acepta posiciones ausentes en código generado o transformado.
Offsets
Las opciones de desmontaje pueden mostrar offsets. Ayudan a interpretar jumps y exception tables.
No son IDs estables entre versiones o builds.
Instrucciones de carga
Constantes, nombres, globals, locals y atributos utilizan operaciones distintas.
x = 10
def usar_global():
return x
def usar_local(x):
return x
El bytecode muestra la diferencia, pero las reglas del lenguaje son la autoridad.
Operaciones binarias
Las expresiones cargan operandos y aplican operaciones.
def calcular(a, b):
return (a + b) * 2
Python moderno puede usar una instrucción general con argumento para la operación concreta.
Constant folding
El compilador puede evaluar determinadas expresiones constantes.
def ejemplo():
return 2 + 3
La salida puede contener solo la constante final. No todas las expresiones se doblan siempre.
Loops
Un for obtiene un iterator, solicita valores y usa jumps.
def totalizar(items):
total = 0
for item in items:
total += item
return total
El flujo ayuda a enseñar iteración, aunque los detalles cambian.
Condiciones y jumps
if, while, booleanos y short-circuit utilizan jumps.
is_jump_target identifica destinos.
for instruccion in dis.get_instructions(funcion):
if instruccion.is_jump_target:
print("destino", instruccion.offset)
Jumps relativos
La representación de jumps y unidades de offset ha cambiado. Usa argval y APIs actuales.
Declara versiones soportadas.
Llamadas
Las llamadas cargan callable y argumentos y usan instrucciones de preparación y ejecución.
La secuencia cambia con métodos, keywords y especialización adaptativa.
Comprehensions
List, set y dict comprehensions pueden producir code objects anidados.
def pares(valores):
return [x * 2 for x in valores if x % 2 == 0]
dis.dis() puede mostrar esos objetos internos.
Generators
Funciones con yield usan instrucciones y flags para suspensión y reanudación.
No manipules su estado mediante internals.
async y await
Coroutines y async generators generan bytecode para espera, envío y reanudación.
La forma varía mucho entre versiones.
Excepciones
El manejo de excepciones ha cambiado significativamente en CPython.
No busques únicamente opcodes históricos para detectar bloques try.
Exception tables
Code objects modernos pueden almacenar tablas compactas fuera del flujo principal.
El formato interno no es contrato estable.
Intérprete adaptativo
CPython puede especializar instrucciones durante la ejecución según los tipos observados.
El desmontaje puede mostrar instrucciones originales o especializadas.
Opción adaptive
Las APIs actuales pueden enseñar instrucciones especializadas.
dis.dis(funcion, adaptive=True)
Ejecuta varias veces antes para observar warm-up, pero los resultados pueden variar.
show_caches
Los inline caches pueden mostrarse con show_caches=True.
dis.dis(funcion, show_caches=True)
Son internals de rendimiento. No los modifiques.
Cambios en runtime
La especialización puede cambiar la forma observada tras warm-up. Una herramienta de comparación debe controlar opciones.
Registra versión, opciones y cantidad de ejecuciones.
stack_effect
dis.stack_effect(opcode, oparg) calcula el efecto sobre la evaluation stack.
import dis
import opcode
efecto = dis.stack_effect(opcode.opmap["LOAD_CONST"], 0)
print(efecto)
Los jumps condicionales pueden requerir considerar el branch.
Análisis de stack
Sumar efectos linealmente no basta con jumps. Un análisis correcto propaga alturas por basic blocks.
Usa un verificador especializado.
Módulo opcode
opcode ofrece mapas y categorías.
import opcode
print(opcode.opmap.get("RETURN_VALUE"))
Los números cambian. Usa nombres de la versión actual.
Implementaciones diferentes
dis describe el bytecode soportado por su runtime, principalmente CPython.
No generalices detalles de CPython a garantías del lenguaje.
Análisis de rendimiento
El bytecode puede mostrar loads repetidos, llamadas en loops y alocaciones. Aun así, la inspección no es medición.
Usa timeit, cProfile y workloads reales.
Microbenchmarks
Compara con warm-up, repetición, aislamiento y control de ruido.
La especialización adaptativa exige medir el estado estable.
Límites de seguridad
La presencia de un opcode no prueba seguridad o peligro. Aliases, imports y comportamiento de objetos rompen allowlists simples.
No crees un sandbox validando bytecode y llamando exec().
Code objects no confiables
No cargues code objects de marshal o pickle no confiables en un proceso crítico solo para desmontarlos.
Aísla y limita recursos.
Diff de bytecode
Al comparar versiones, normaliza offsets, caches y campos inestables. Compara nombres y argumentos semánticos.
Separa cambios del lenguaje de diferencias de implementación.
Línea de comandos
El módulo puede desmontar un archivo.
python -m dis modulo.py
Es útil para aprendizaje e inspección rápida.
Combina con AST
AST muestra estructura de alto nivel y dis instrucciones después de compilar.
Consulta ast en Python.
Combina con tokenize
tokenize conserva comentarios y grafía que desaparecen en bytecode.
Consulta tokenize en Python.
Tests de herramientas
Ejecuta la suite en cada versión soportada. Incluye funciones simples, closures, generators, async, comprehensions, excepciones y pattern matching.
Usa fixtures específicas por versión.
Errores comunes
Los fallos frecuentes son asumir opcodes estables, parsear texto de dis, comparar offsets entre versiones, ignorar caches adaptativos, optimizar sin medir, tratar bytecode como sandbox, generalizar CPython y cargar code objects no confiables.
Conclusión
dis muestra cómo Python compila y ejecuta código al nivel de bytecode. Usa get_instructions() para análisis programático, controla caches y especialización y registra siempre la versión.
Trata el bytecode como detalle cambiante y perfila antes de optimizar. Consulta la documentación oficial de dis y la documentación de opcode.







