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

    Chic portrait of a woman wearing trendy sunglasses reflecting numbers, captured in a modern setting.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    struct en Python: datos binarios

    Aprende struct en Python para empaquetar datos binarios, controlar endianness, reutilizar buffers y validar protocolos externos.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tarfile en Python: crea TAR seguro

    Aprende tarfile en Python para crear TAR comprimido, inspeccionar miembros y extraer con filtros, límites y protección de rutas.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    Row of colorful office binders neatly arranged on a shelf, ideal for organization concepts.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    gzip en Python: comprime archivos .gz

    Aprende gzip en Python para leer y escribir .gz, crear streams reproducibles, procesar datos grandes y limitar la expansión externa.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    Neatly arranged blue office binders labeled with dates and names for organized storage.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    lzma en Python: comprime archivos XZ

    Aprende lzma en Python para crear archivos XZ, procesar streams, elegir checks y filtros y limitar memoria con datos externos.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    A close-up shot showcasing the intricate scales of a snake, highlighting texture and color.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    bz2 en Python: comprime con bzip2

    Aprende bz2 en Python para comprimir archivos y bytes, procesar streams por bloques y limitar la expansión de datos externos.

    Ler mais

    Tempo de leitura: 4 minutos
    16/08/2026
    Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    zlib en Python: comprime datos

    Aprende zlib en Python para comprimir y descomprimir bytes, procesar streams, usar checksums y limitar datos externos.

    Ler mais

    Tempo de leitura: 6 minutos
    16/08/2026