CPython, PyPy y MicroPython: implementaciones distintas de Python

Cuando aprendes español, puedes hablarlo con acento madrileño, con acento porteño o con acento caribeño. El idioma es el mismo; quien lo ejecuta es diferente. Con Python pasa exactamente lo mismo: Python es una especificación del lenguaje, y una implementación es el programa concreto que lee tu código y lo hace funcionar en una máquina real.

Esto importa porque cuando instalas Python desde python.org y escribes python script.py, no estás ejecutando “Python el lenguaje”. Estás ejecutando CPython, que es una forma de ejecutar ese lenguaje. Existen otras, con decisiones de diseño distintas, velocidades distintas y casos de uso distintos. Saber esto te ahorra mucha confusión cuando leas documentación que dice “comportamiento específico de CPython” o cuando alguien te diga que PyPy es más rápido.

Tres implementaciones, tres propósitos

CPython es la implementación de referencia. Está escrita en C, la mantiene el equipo central de Python, y es el estándar contra el que todas las demás implementaciones se comparan. Cuando el lenguaje evoluciona, CPython es la primera en implementar los cambios. Su mecanismo interno es directo: traduce tu .py a bytecode (instrucciones intermedias de bajo nivel) y luego un intérprete en C ejecuta ese bytecode línea por línea. Rápido de arrancar, predecible, universal. Si instalas una librería de PyPI y funciona en todas partes, es porque casi todo el ecosistema asume CPython.

PyPy es una implementación alternativa escrita mayormente en RPython (un subconjunto restringido de Python). Su diferencia clave es que incluye un compilador JIT (Just-In-Time): en vez de interpretar bytecode cada vez, PyPy observa qué partes de tu código se ejecutan repetidamente y las compila a código máquina nativo en tiempo de ejecución. Para bucles intensivos y cálculos numéricos puros, la aceleración real puede ser 5× o 10× respecto a CPython. La razón por la que no reemplaza a CPython es que no todas las extensiones escritas en C (como partes de NumPy o SciPy) son compatibles sin adaptaciones, y el tiempo de arranque inicial es mayor.

MicroPython es Python rediseñado para vivir en microcontroladores con kilobytes (no gigabytes) de RAM: placas como Raspberry Pi Pico, ESP32 o STM32. Implementa un subconjunto de Python 3, omite módulos de la biblioteca estándar que simplemente no caben, y tiene APIs propias para controlar pines GPIO, I2C o SPI directamente desde Python. El mismo bucle for funciona igual; import os puede comportarse diferente o no existir.

La razón por la que el mismo código puede comportarse diferente en cada implementación tiene que ver con dos cosas: partes de la especificación que son intencionalmente no deterministas (como el orden de iteración histórico de los diccionarios antes de Python 3.7), y comportamientos que CPython exhibe como efecto secundario de su implementación en C pero que no son parte del contrato del lenguaje. El ejemplo clásico: en CPython, los enteros pequeños entre -5 y 256 se reutilizan en memoria (son el mismo objeto). En otra implementación, no necesariamente.

# implementaciones.py
# Ejecuta esto en CPython y luego en PyPy para ver la diferencia de velocidad

import time

def suma_cuadrados(n: int) -> int:
    """Bucle numérico puro: el tipo de carga donde PyPy brilla más."""
    total = 0
    for i in range(n):
        total += i * i
    return total

ITERACIONES = 10_000_000

inicio = time.perf_counter()
resultado = suma_cuadrados(ITERACIONES)
duracion = time.perf_counter() - inicio

print(f"Resultado : {resultado}")
print(f"Tiempo    : {duracion:.3f} segundos")

# --- Demuestra el comportamiento específico de CPython ---
a = 256
b = 256
# En CPython, los enteros pequeños son cacheados; 'a' y 'b' apuntan al mismo objeto.
# 'is' compara identidad de objeto en memoria, NO igualdad de valor.
print(f"a is b (256): {a is b}")   # True en CPython, no garantizado en otras

x = 1000
y = 1000
# Fuera del rango cacheado, CPython puede crear objetos distintos.
print(f"x is y (1000): {x is y}")  # Generalmente False en CPython; varía

Lo que el código te está enseñando

La función suma_cuadrados es deliberadamente simple: un bucle que solo hace aritmética entera. Ese es exactamente el patrón donde el JIT de PyPy tiene más margen para optimizar. El JIT detecta que el bucle se ejecuta millones de veces con los mismos tipos, compila esa ruta a código máquina, y las iteraciones siguientes corren sin pasar por el intérprete. CPython, en cambio, despacha cada operación a través de su máquina de bytecode en cada iteración.

El bloque de is con enteros ilustra algo importante: is compara identidad de objeto (si dos nombres apuntan exactamente a la misma dirección en memoria), no igualdad de valor. El hecho de que 256 is 256 sea True en CPython no es una promesa del lenguaje; es un detalle de implementación del caché de enteros pequeños. El código correcto para comparar valores siempre usa ==. Usar is con números o strings literales te da resultados que dependen de la implementación, del intérprete, e incluso de cómo esté empaquetado el código.

Lo mismo aplica a los módulos: si tu código usa ctypes, sys.getrefcount() o cualquier cosa marcada como [específico de CPython] en la documentación o en este índice, significa que ese comportamiento existe porque CPython está escrito en C y expone detalles de esa implementación. PyPy puede tener un equivalente, MicroPython probablemente no lo tenga, y confiar en ello hace tu código frágil.

Errores que debes conocer

Error: usar is para comparar valores enteros o strings, asumiendo que funcionará igual en todas partes, cuando en realidad depende del caché de objetos de CPython.

# ❌ Wrong
def es_admin(nivel: int) -> bool:
    return nivel is 1   # Funciona "por casualidad" en CPython con enteros pequeños

# ✅ Right
def es_admin(nivel: int) -> bool:
    return nivel == 1   # Compara valor, no identidad; correcto en toda implementación

== compara el contenido lógico del objeto; is compara si son literalmente el mismo objeto en memoria. Para valores, siempre ==.

Error: asumir que toda librería de PyPI funciona igual en PyPy o MicroPython, instalarla, y descubrir que falla porque tiene extensiones en C nativo que no son compatibles.

# ❌ Wrong — en MicroPython o PyPy sin soporte C-extensions
import numpy as np          # Puede no existir o ser una reimplementación parcial
resultado = np.array([1, 2, 3])

# ✅ Right — verifica la documentación de tu implementación antes
# En MicroPython: usa ulab (subconjunto de NumPy para microcontroladores)
# En PyPy: usa la versión de NumPy compilada específicamente para PyPy
import ulab.numpy as np     # API similar, diseñada para correr con recursos mínimos
resultado = np.array([1, 2, 3])

Antes de elegir una librería, confirma que tiene soporte explícito para la implementación que usas.

4

Dejar un comentario

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

Scroll al inicio