
Recursión y por qué Python (no) la ama 🌀
Si en una entrevista te piden escribir el factorial o los números de Fibonacci mediante recursión, hazlo. Pero si la usas a menudo en la realidad...
Sí, la recursión es conceptualmente hermosa, matemáticamente elegante, pero si te excedes, puedes tumbar la producción y llenar toda la RAM con marcos de pila.
Analicemos qué sucede bajo el capó y por qué Python, a diferencia de los lenguajes funcionales, no aprecia la recursión.
1️⃣ El costo: la pila de llamadas
Cada llamada a función no es gratuita. Crea un nuevo marco de pila en memoria. En este marco se almacenan las variables locales y la dirección de retorno.
Imagina una muñeca rusa donde cada muñeca siguiente pesa lo mismo que la anterior. En Python, ese "peso" recae en la RAM.
Un bucle
while o for usa un solo bloque de memoria. La recursión consume memoria linealmente (o exponencialmente) en proporción a la profundidad de la llamada.2️⃣ Por qué no hay optimización de cola
En lenguajes como Haskell o C++, el compilador es inteligente. Si la llamada recursiva es la última acción en la función (recursión de cola), la reemplaza con un simple salto (
GOTO), sin crear un nuevo marco. Esto se llama Optimización de Llamada de Cola (TCO).En Python no hay TCO ni la habrá. Guido van Rossum (BDFL) está fundamentalmente en contra de TCO en Python. El argumento es simple: "Queremos ver trazas de error completas". Si colapsas la pila, nunca sabrás en qué vuelta de la recursión falló todo.
Por lo tanto, el límite de 1000 llamadas (por defecto) no es un error, sino un protector contra el desbordamiento de pila. ¿Se puede aumentar mediante
sys.setrecursionlimit? Se puede. Pero si lo necesitas, hay un 99% de probabilidad de que hayas errado en la arquitectura.3️⃣ ¿Cuándo la recursión es mala y cuándo es necesaria?
❌ Estructuras lineales (por ejemplo, listas): Cálculo de factorial, números de Fibonacci o recorrido de una lista plana. Un bucle iterativo while/for siempre será más rápido y eficiente en memoria (memoria O(1) frente a O(N)).
✅ Estructuras ramificadas (árboles, grafos, dicts anidados): Análisis de JSON, recorrido del sistema de archivos, árbol DOM o algoritmos de Divide y Vencerás (QuickSort, MergeSort). Aquí la recursión reduce la carga cognitiva y hace el código legible.
4️⃣ Remedio contra la lentitud: @lru_cache
El ejemplo clásico de estupidez es calcular Fibonacci "a lo bruto". Complejidad O(2^n). Esto significa que para calcular el número 50, el programa morirá antes de terminar.
Solución: Memoización.
El decorador
@functools.lru_cache almacena en caché los resultados de las llamadas. Si la función ya se ha calculado con ese argumento, Python simplemente obtiene el valor listo de la tabla hash. Esto convierte el horror exponencial en complejidad lineal O(N).En resumen: Python no es Haskell ni Lisp. Aquí la recursión es un ciudadano de segunda clase. Úsala para árboles y grafos, pero para todo lo demás, hay bucles.
#anatomía_de_python
Comentarios
0Aún no hay comentarios.
Inicia sesión para participar en la conversación.