dis en Python: entiende el bytecode

Publicado el: 03/08/2026
Tempo de leitura: 7 minutos
Monitor con código binario que representa análisis de bytecode con dis en Python

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.py

Las opciones actuales incluyen:

  • -C para cachés inline;
  • -O para offsets;
  • -P para posiciones;
  • -S para 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 total

dis 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 operaciones

Evita 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_code directamente.
  • 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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Pantalla de error que representa diagnóstico de crashes y deadlocks con faulthandler en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    faulthandler en Python: diagnostica bloqueos

    Aprende faulthandler en Python para diagnosticar crashes, deadlocks y timeouts mediante pilas de threads y código nativo.

    Ler mais

    Tempo de leitura: 7 minutos
    03/08/2026
    Portátil con código que representa análisis de traceback y depuración en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    traceback en Python: errores y pila

    Aprende traceback en Python para capturar, formatear y registrar pilas de error sin filtrar datos sensibles ni retener memoria.

    Ler mais

    Tempo de leitura: 6 minutos
    03/08/2026
    Análisis de software que representa introspección de objetos con inspect en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    inspect en Python: introspección de objetos

    Aprende inspect en Python para analizar funciones, clases, firmas, código fuente, decorators, generators, coroutines y frames con seguridad.

    Ler mais

    Tempo de leitura: 6 minutos
    02/08/2026
    Módulo de memoria que representa referencias débiles y cachés en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    weakref en Python: referencias débiles

    Aprende weakref en Python para crear referencias débiles, cachés automáticas, observadores y finalizadores sin retener objetos en memoria.

    Ler mais

    Tempo de leitura: 9 minutos
    28/07/2026
    Icono de archivo ZIP para un artículo sobre zipfile en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    zipfile en Python: archivos ZIP seguros

    Aprende a crear, leer, validar y extraer archivos ZIP con zipfile en Python de forma predecible y segura.

    Ler mais

    Tempo de leitura: 5 minutos
    27/07/2026
    Grafo de dependencias y flujo de tareas en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    graphlib en Python: ordenación topológica

    Aprende graphlib en Python para ordenar dependencias, detectar ciclos y coordinar tareas independientes en paralelo.

    Ler mais

    Tempo de leitura: 6 minutos
    27/07/2026