El código fuente de Python se compila en instrucciones intermedias ejecutadas por la máquina virtual del intérprete. En CPython, esas instrucciones forman el bytecode. El módulo dis en Python desmonta funciones, métodos, clases, generators, coroutines y fragmentos de código, mostrando cargas de variables, llamadas, saltos, operadores, cachés adaptativas y la relación entre posiciones del código fuente y operaciones internas.
Esta guía explica dis.dis(), Bytecode, get_instructions(), code_info(), análisis de tracebacks y bytecode especializado. Complementa nuestros artículos sobre scripts Python lentos, cProfile, timeit, pdb e inspect.
Qué es el bytecode
Cuando CPython carga una función, compila la sintaxis en un objeto de código. Ese objeto contiene constantes, nombres, variables locales, flags, posiciones de origen y una secuencia de instrucciones de la máquina virtual.
def doble(numero):
return numero * 2
print(doble.__code__)
print(doble.__code__.co_consts)
print(doble.__code__.co_varnames)El bytecode no es código de máquina del procesador. Es una representación específica del intérprete que consume el loop de evaluación de CPython.
Primera descompilación con dis()
dis.dis() imprime las instrucciones de una función.
import dis
def doble(numero):
return numero * 2
dis.dis(doble)La salida puede incluir operaciones como RESUME, LOAD_FAST, LOAD_CONST, BINARY_OP y RETURN_VALUE. Los nombres y detalles cambian entre versiones.
El bytecode es un detalle de implementación
La documentación oficial de dis indica que el bytecode de CPython es un detalle interno. Las instrucciones pueden añadirse, eliminarse o modificarse y otras máquinas virtuales de Python pueden usar un modelo distinto.
Usa la descompilación para aprendizaje, diagnóstico, pruebas del compilador y herramientas vinculadas a una versión. No conviertas los opcodes en un contrato portable de negocio.
Analizar strings y objetos de código
dis() también acepta strings de código, módulos, clases, métodos, coroutines y objetos devueltos por compile().
dis.dis("resultado = sumar(a, b)")
codigo = compile(
"total = precio * cantidad",
"ejemplo.py",
"exec",
)
dis.dis(codigo)Para una clase o módulo, puede recorrer objetos de código anidados.
Controlar la profundidad
Las funciones internas, comprehensions y expresiones generadoras tienen objetos de código propios. El argumento depth controla la recursión.
def externa():
def interna(valor):
return valor + 1
return [interna(x) for x in range(3)]
dis.dis(externa, depth=0)
dis.dis(externa, depth=2)Usa una profundidad pequeña en módulos o clases grandes para mantener legible el informe.
Información del objeto de código
code_info() devuelve un resumen con cantidad de argumentos, variables, constantes, nombres, flags y tamaño de pila.
print(dis.code_info(externa))show_code() imprime información equivalente.
dis.show_code(externa)Estos datos proporcionan contexto antes de examinar cada instrucción.
La clase Bytecode
dis.Bytecode envuelve el objeto analizado y permite obtener texto formateado o iterar por operaciones.
bytecode = dis.Bytecode(doble)
print(bytecode.info())
print(bytecode.dis())
for instruccion in bytecode:
print(instruccion.opname, instruccion.argrepr)Acepta funciones, métodos, generators, coroutines, strings y objetos de código.
Análisis estructurado con get_instructions()
Las herramientas que necesitan procesar operaciones en lugar de imprimir texto deberían usar get_instructions().
for instruccion in dis.get_instructions(doble):
print({
"operacion": instruccion.opname,
"offset": instruccion.offset,
"argumento": instruccion.argval,
"linea": instruccion.line_number,
})Cada objeto Instruction expone opcode, nombre, argumento resuelto, posición, destino de salto, operación base e información de caché.
Operaciones LOAD y STORE
Las instrucciones que comienzan con LOAD colocan referencias o valores en la pila de evaluación. Las operaciones STORE retiran un valor y lo asignan a una variable, atributo o contenedor.
def ejemplo(valor):
resultado = valor + 10
return resultado
dis.dis(ejemplo)LOAD_FAST accede a una variable local, LOAD_CONST carga una constante y STORE_FAST escribe en un slot local. Globales, atributos y closures utilizan instrucciones diferentes.
La pila de evaluación
Muchas operaciones consumen operandos de la pila y colocan un resultado. Para una suma, el intérprete carga dos valores y ejecuta una operación binaria.
def sumar(a, b):
return a + b
dis.dis(sumar)dis.stack_effect() calcula el cambio neto causado por un opcode.
opcode = dis.opmap["LOAD_CONST"]
print(dis.stack_effect(opcode, 0))Es útil para validadores, simuladores y herramientas relacionadas con compiladores.
Saltos y flujo de control
Condiciones, loops, cortocircuitos, pattern matching y excepciones se representan mediante comparaciones y saltos.
def clasificar(numero):
if numero >= 0:
return "positivo"
return "negativo"
dis.dis(clasificar, show_offsets=True)Las versiones recientes muestran labels lógicos para los destinos. Evita calcular offsets manualmente sin considerar cachés y reglas específicas de la versión.
Posiciones de código en Python 3.14
Python 3.14 añade show_positions=True y la opción -P. La salida puede incluir líneas inicial y final, además de columnas.
dis.dis(
clasificar,
show_offsets=True,
show_positions=True,
)Las posiciones detalladas ayudan a depuradores, cobertura, análisis estático y mensajes que señalan una expresión concreta.
Ejecutar dis desde la terminal
El módulo desmonta un archivo o código recibido por entrada estándar.
python -m dis programa.pyLas opciones actuales incluyen:
-Cpara cachés inline;-Opara offsets;-Ppara posiciones;-Spara bytecode especializado.
Las opciones dependen de la versión instalada.
Cachés inline
Desde Python 3.11, algunas instrucciones reservan entradas de caché para la especialización del intérprete. Usa show_caches=True para verlas.
dis.dis(doble, show_caches=True)Los datos pertenecen lógicamente a la instrucción anterior. No interpretes bytes de caché poblados como operaciones independientes ni modifiques bytecode adaptativo bruto.
Bytecode adaptativo y especializado
CPython puede especializar operaciones después de observar tipos y patrones durante la ejecución. Usa adaptive=True o -S en la CLI de Python 3.14.
for _ in range(20_000):
doble(10)
dis.dis(doble, adaptive=True, show_caches=True)La especialización es dinámica. El resultado varía según calentamiento, build, arquitectura y estado del proceso.
La descompilación no es un benchmark
Menos instrucciones visibles no garantizan mejor rendimiento. Una sola operación puede llamar código C complejo, crear objetos o realizar I/O. Confirma hipótesis con timeit, cProfile y métricas reales.
def con_sum(valores):
return sum(valores)
def con_loop(valores):
total = 0
for valor in valores:
total += valor
return totaldis explica diferencias estructurales; el benchmark mide su impacto.
Analizar un fallo con distb()
dis.distb() desmonta la función superior de un traceback y marca la instrucción asociada a la excepción.
try:
valor = (1, 2)[5]
except IndexError as error:
dis.distb(error.__traceback__)Bytecode.from_traceback() ofrece una alternativa estructurada.
bytecode = dis.Bytecode.from_traceback(error.__traceback__)
print(bytecode.dis())Este recurso se combina con nuestra guía de traceback en Python.
Closures y variables libres
Las funciones internas pueden acceder a celdas del scope externo. La salida puede mostrar MAKE_CELL, LOAD_DEREF y COPY_FREE_VARS.
def multiplicador(factor):
def aplicar(valor):
return valor * factor
return aplicar
dis.dis(multiplicador)Estas operaciones permiten visualizar cómo se conservan referencias en closures.
Generators y coroutines
Generators, yield from, await y async generators utilizan instrucciones como RETURN_GENERATOR, YIELD_VALUE, SEND y GET_AWAITABLE.
async def buscar(cliente):
return await cliente.obtener()
dis.dis(buscar)El bytecode asíncrono cambia bastante entre versiones a medida que evoluciona el intérprete.
Comprehensions
Una list comprehension puede compilarse como objeto de código anidado o recibir optimizaciones específicas.
def dobles(limite):
return [n * 2 for n in range(limite)]
dis.dis(dobles, depth=2)Observa iteración, anexado y llamadas, pero no reescribas una comprensión solamente porque una salida parezca más larga.
Colecciones de opcodes
El módulo expone colecciones para introspección automática.
print(dis.opmap["RETURN_VALUE"])
print(dis.opname[dis.opmap["RETURN_VALUE"]])
print(dis.hasconst)
print(dis.hasjump)opmap asocia nombres a códigos y opname realiza el camino inverso. hasconst, hasname, hasfree y hasjump clasifican operaciones.
Tests que verifican bytecode
Las bibliotecas próximas al intérprete pueden verificar una operación, pero los tests deben depender de implementación y versión.
import sys
operaciones = {
item.opname
for item in dis.get_instructions(doble)
}
if sys.implementation.name == "cpython":
assert "RETURN_VALUE" in operacionesEvita snapshots de texto rígidos porque labels, offsets, cachés y formato cambian.
No importes código no confiable
Desmontar un string compila sin ejecutarlo, pero analizar un módulo puede llevar a importarlo. Los imports ejecutan código de nivel superior. Procesa proyectos no confiables en un proceso aislado, sin credenciales y con permisos restringidos.
Errores frecuentes
- Tratar opcodes como API pública estable.
- Comparar bytecode de versiones diferentes sin contexto.
- Inferir velocidad solo por cantidad de instrucciones.
- Ignorar cachés inline y especialización.
- Construir herramientas con reglas antiguas de offsets.
- Importar módulos no confiables para analizarlos.
- Modificar
co_codedirectamente. - Usar snapshots textuales frágiles.
Buenas prácticas
- Registra versión e implementación de Python.
- Usa
get_instructions()para análisis estructurado. - Prefiere posiciones y labels a cálculos frágiles.
- Confirma ideas de rendimiento con benchmarks.
- Limita profundidad en objetos grandes.
- Analiza bytecode adaptativo después de calentamiento controlado.
- Aísla código desconocido.
- Considera normales los cambios entre releases.
Conclusión
El módulo dis en Python revela cómo CPython traduce construcciones del lenguaje en operaciones de la máquina virtual. Ayuda a comprender variables locales, llamadas, condiciones, loops, closures, generators, excepciones, cachés y especialización adaptativa.
Esta visión es poderosa, pero está vinculada a una implementación y versión. Usa la descompilación como herramienta de aprendizaje y diagnóstico, no como contrato portable ni sustituto de mediciones. Con Bytecode, get_instructions(), posiciones de origen y comparaciones controladas, puedes investigar el intérprete sin depender de suposiciones frágiles sobre opcodes.







