Qué es un intérprete y cómo ejecuta código Python

Cuando escribes x = 5 + 3 en un archivo .py, no está pasando nada todavía. Esas son letras en un fichero de texto. El intérprete es el programa que lee ese texto y lo convierte en acciones reales sobre tu hardware: mueve bits en memoria, llama al sistema operativo, enciende transistores. La distancia entre “instrucción escrita” y “instrucción ejecutada” la cubre él.

¿Por qué existe esa separación? Porque tu CPU no habla Python. Habla un lenguaje binario específico de su arquitectura — instrucciones x86, ARM, o lo que sea. Alguien tiene que traducir. Los lenguajes resuelven esto de dos maneras clásicas: o compilas todo el código a lenguaje máquina antes de correrlo (como C), o tienes un programa que lee y ejecuta tu código en el momento (como un intérprete). La primera es el traductor que entrega un libro terminado; la segunda es el intérprete simultáneo en una conferencia que procesa frase por frase.

La intuición de “frase por frase” es útil para arrancar, pero Python en la práctica no es tan simple. Cuando ejecutas python script.py, el intérprete no lee tu código fuente directamente línea por línea. Primero hace un paso de compilación a bytecode: una representación intermedia, más compacta, diseñada para una máquina virtual. Ves ese bytecode si miras los archivos .pyc dentro de __pycache__/. Después, la máquina virtual de Python (también llamada PVM) ejecuta ese bytecode instrucción por instrucción. Así que el proceso real es: código fuente → bytecode → ejecución en la VM.

¿Cuándo te importa esto? Importa cuando entiendes por qué Python es más lento que C para código de cálculo puro (hay una VM en medio), por qué los errores de sintaxis los ves antes de que corra ninguna línea (se detectan en la compilación a bytecode), y por qué los errores de lógica o de tipos solo aparecen cuando la VM llega a esa instrucción en particular.

Lo que se rompe si no entiendes esto: podrías asumir que un if False: error_aquí() nunca causa problemas porque “nunca se ejecuta”. Para un error de lógica, correcto. Pero si esa línea tiene un error de sintaxis, la compilación a bytecode falla igual, antes de correr nada.

# script.py — ejemplo completo y ejecutable

def add(a, b):
    return a + b

def greet(name):
    # Este string se construye en tiempo de ejecución, no antes
    message = f"Hola, {name}!"
    return message

result = add(10, 20)
print(greet("Marta"))
print(f"10 + 20 = {result}")

# Forzamos ver el bytecode del módulo actual
import dis

print("\n--- Bytecode de la función add ---")
dis.dis(add)
Hola, Marta!
10 + 20 = 30

--- Bytecode de la función add ---
  2           0 RESUME                   0

  3           2 LOAD_FAST                0 (a)
              4 LOAD_FAST                1 (b)
              6 BINARY_OP               0 (+)
             10 RETURN_VALUE

El módulo dis de la biblioteca estándar te muestra exactamente lo que la VM va a ejecutar. Fíjate en LOAD_FAST: eso es una instrucción de bytecode que empuja el valor de la variable local a al stack interno de la VM. La suma a + b no es una operación atómica — son tres instrucciones: cargar a, cargar b, ejecutar BINARY_OP. La VM las procesa en secuencia.

Ese nivel de detalle tiene implicaciones concretas. Cuando Python ejecuta add(10, 20), la VM llega a LOAD_FAST 0, busca el valor de a en el frame de la llamada, lo pone en el stack. Llega a LOAD_FAST 1, hace lo mismo con b. Llega a BINARY_OP, saca los dos valores del stack, invoca la lógica de suma (que en Python implica buscar el método __add__ del objeto int), y empuja el resultado de vuelta. Todo esto por un simple a + b. No es ineficiente para uso normal, pero explica por qué Python no es la primera elección para loops de millones de iteraciones sin ayuda de librerías como NumPy, que salen de la VM para las operaciones pesadas.

La función greet ilustra otro punto: el f-string f"Hola, {name}!" no se evalúa cuando Python compila el archivo. Se evalúa cuando la VM ejecuta esa línea. Si name fuese un número o un objeto raro, Python lo convertiría a string en ese momento. La compilación a bytecode solo verifica estructura, no valores.

Errores que debes conocer

Error: asumir que un bloque inalcanzable no puede fallar en compilación.

# ❌ Wrong
if False:
    def broken(:   # SyntaxError: aquí hay un error de sintaxis
        pass

print("Esto nunca llega a ejecutarse")
# ✅ Right
if False:
    def ok():
        pass

print("Ahora sí arranca")

El paso de compilación a bytecode analiza todo el archivo, no solo los caminos que se ejecutarán. Un error de sintaxis dentro de un if False aborta el programa antes de que corra ninguna línea.

Error: confundir el momento de detección de errores de sintaxis con errores de nombre o tipo.

# ❌ Wrong — asumes que NameError se detecta igual que SyntaxError
def risky():
    print(variable_que_no_existe)  # NameError, pero solo al llamar la función

risky()  # El error ocurre aquí, en ejecución
# ✅ Right — separas mentalmente los dos momentos
def risky():
    print(variable_que_no_existe)

# Si nunca llamas a risky(), el programa termina sin error.
# El intérprete no pre-valida nombres, solo estructura sintáctica.

NameError, TypeError, AttributeError — todos viven en tiempo de ejecución. Solo la sintaxis se verifica antes. Esa distinción te ahorrará muchas confusiones cuando un bug aparezca en producción en código que “nunca se usaba”.

2

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio