Visualización de la pila de llamadas
Use el módulo sys de Python y trazas con print para observar cómo crecen y disminuyen los marcos de pila, y comprenda los riesgos de desbordamiento de pila en recursiones profundas.
Visualización de la pila de llamadas es una lección gratuita de Coding Interview Prep en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Coding Interview Prep, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Coding Interview Prep incluye 4 lecciones en total.
¿Qué es la pila de llamadas?
Cada llamada a una función en Python crea un marco de pila en la pila de llamadas. El marco almacena las variables locales de la función, su dirección de retorno (dónde se reanuda la ejecución después de que la función retorna) y el puntero de instrucción actual. Cuando una función retorna, su marco se extrae de la pila y el control vuelve a quien la llamó. La pila de llamadas crece hacia abajo con cada llamada y se reduce con cada retorno.
Comprender la pila de llamadas es fundamental para depurar código recursivo, estimar el uso de memoria y evitar errores de desbordamiento de la pila en recursiones profundas.
import traceback
def outer():
inner()
def inner():
# Print the current call stack
traceback.print_stack()
outer()
# Shows: module -> outer -> innerObserve los marcos de pila con sys
El módulo sys de Python proporciona herramientas para inspeccionar la pila de llamadas durante la ejecución. sys._getframe(n) devuelve el marco de pila situado n niveles por encima de la función actual. Cada marco tiene un diccionario f_locals con las variables locales y f_code.co_name para el nombre de la función. Insertar mensajes de depuración dentro de una función recursiva revela cómo se acumulan y desaparecen los marcos.
import sys
def countdown(n):
depth = 0
frame = sys._getframe(0)
while frame:
depth += 1
frame = frame.f_back
print(' ' * (n * 2) + f'countdown({n}) called, stack depth={depth}')
if n <= 0:
return
countdown(n - 1)
print(' ' * (n * 2) + f'countdown({n}) returning')
countdown(3)Trace el factorial en la pila de llamadas
Trace factorial(4) en la pila de llamadas. Las llamadas se acumulan: factorial(4) llama a factorial(3), que llama a factorial(2), que llama a factorial(1), que llama a factorial(0). En el caso base, la pila tiene 5 marcos. Los retornos se desenrollan: factorial(0) devuelve 1; factorial(1) devuelve 1×1=1; factorial(2) devuelve 2×1=2; factorial(3) devuelve 3×2=6; factorial(4) devuelve 4×6=24. La profundidad es igual a n+1 y la complejidad espacial es O(n).
def factorial(n, indent=0):
prefix = ' ' * indent
print(prefix + f'-> factorial({n})')
if n == 0:
print(prefix + '<- returns 1')
return 1
result = n * factorial(n - 1, indent + 1)
print(prefix + f'<- returns {result}')
return result
factorial(4)Desbordamiento de la pila: límite de recursión de Python
Python genera RecursionError cuando la pila de llamadas supera su límite (aproximadamente 1000 marcos de forma predeterminada). Esto protege contra la recursión infinita que consumiría toda la memoria. Para problemas con un tamaño de entrada n = 10^4 o superior, una solución recursiva con profundidad O(n) fallará si no se aumenta el límite. La solución iterativa equivalente utiliza espacio O(1) en la pila porque solo usa un marco para la función envolvente.
import sys
print('Recursion limit:', sys.getrecursionlimit())
def deep_recursion(n):
if n == 0:
return 0
return 1 + deep_recursion(n - 1)
# Safe: within limit
try:
print(deep_recursion(900))
except RecursionError:
print('Overflow at 900')
# Overflow
try:
print(deep_recursion(2000))
except RecursionError:
print('RecursionError at 2000 — limit exceeded!')Aumento del límite de recursión
Puede aumentar el límite de recursión de Python con sys.setrecursionlimit(n), pero esto solo es un parche provisional. El límite predeterminado existe porque cada marco de pila ocupa memoria (normalmente varios cientos de bytes en CPython). Establecer el límite en 10^6 y después ejecutar una recursión de profundidad 10^5 puede asignar cientos de megabytes de espacio de pila. La solución correcta suele ser convertir el código en una solución iterativa o usar memoización para reducir la profundidad.
import sys
# Only increase when you are certain of the maximum depth
# and have confirmed it is safe
original = sys.getrecursionlimit()
sys.setrecursionlimit(5000)
def sum_to(n):
if n == 0:
return 0
return n + sum_to(n - 1)
print(sum_to(3000)) # Works with increased limit
sys.setrecursionlimit(original) # restore
print('Limit restored:', sys.getrecursionlimit())La pila de llamadas en la recursión mutua
La recursión mutua ocurre cuando la función A llama a la función B y la función B llama a la función A. La pila de llamadas alterna entre marcos de A y B. Este patrón aparece al determinar si un número es par o impar y en simulaciones de máquinas de estados. Es correcto siempre que la profundidad de la pila permanezca limitada, pero puede ser más difícil razonar sobre la profundidad que en una recursión lineal sencilla.
def is_even(n):
if n == 0:
return True
return is_odd(n - 1)
def is_odd(n):
if n == 0:
return False
return is_even(n - 1)
# Stack alternates: is_even(4)->is_odd(3)->is_even(2)->is_odd(1)->is_even(0)
print(is_even(4)) # True
print(is_odd(5)) # True
print(is_even(7)) # FalseLlamadas de cola y por qué Python no las optimiza
Una llamada de cola es una llamada recursiva que constituye la última operación antes de retornar; no se realiza ningún cálculo después de ella. En lenguajes como Haskell o Scheme, las llamadas de cola se optimizan como bucles (optimización de llamadas de cola, TCO), lo que proporciona un espacio de pila O(1). Python no implementa deliberadamente TCO. Como explicó Guido van Rossum, conservar el seguimiento completo de la pila para la depuración era más valioso que ahorrar espacio. Por eso, en Python, el código recursivo de cola sigue utilizando un espacio de pila O(n).
# Tail-recursive factorial (accumulator pattern)
def factorial_tail(n, acc=1):
if n == 0:
return acc
return factorial_tail(n - 1, acc * n) # tail call
# In Python, this still uses O(n) stack space (no TCO)
# But it IS semantically tail-recursive
print(factorial_tail(6)) # 720
print(factorial_tail(10)) # 3628800
# Iterative version: same logic, O(1) stack
def factorial_iter(n):
acc = 1
while n > 0:
acc *= n
n -= 1
return acc
print(factorial_iter(10)) # 3628800Impresión de árboles de recursión
Visualizar el árbol de recursión ayuda a identificar dónde aparecen subproblemas duplicados (el objetivo de la memoización). Una forma sencilla de imprimir el árbol es añadir un parámetro indent que aumente en 2 espacios por nivel. Cada llamada imprime sus argumentos al entrar y su valor de retorno al salir. Ejecutarlo para Fibonacci(5) muestra claramente la ramificación exponencial y las llamadas repetidas.
def fib_traced(n, indent=0):
prefix = ' ' * indent
print(prefix + f'fib({n})')
if n <= 1:
print(prefix + f'=> {n}')
return n
result = fib_traced(n-1, indent+1) + fib_traced(n-2, indent+1)
print(prefix + f'=> {result}')
return result
fib_traced(4)
# Shows the branching tree with duplicated sub-problemsProfundidad de pila = complejidad espacial
En cualquier función recursiva, la profundidad máxima de la pila de llamadas es igual a la profundidad máxima de la recursión en cualquier momento de la ejecución. Esta profundidad equivale directamente a la complejidad espacial auxiliar. En la recursión lineal (factorial, Fibonacci, inversión de cadenas), la profundidad es O(n). En los algoritmos de divide y vencerás (ordenación por mezcla, búsqueda binaria), la profundidad es O(log n). En los recorridos de árboles, la profundidad es O(h), donde h es la altura del árbol (O(log n) si está equilibrado, O(n) en el peor caso).
# Recursion depth = space complexity
# Linear recursion: O(n) stack
def linear_depth(n):
if n == 0: return 0
return 1 + linear_depth(n - 1) # depth = n
# Logarithmic recursion: O(log n) stack
def log_depth(n):
if n <= 1: return 0
return 1 + log_depth(n // 2) # depth = log2(n)
print('n=32 linear depth:', 32)
print('n=32 log depth:', log_depth(32)) # 5
print('n=1024 log depth:', log_depth(1024)) # 10Conversión de recursión a iteración con una pila explícita
Cualquier algoritmo recursivo puede convertirse en iterativo mediante la gestión explícita de la pila de llamadas con una lista de Python. En lugar de permitir que el sistema operativo gestione los marcos, se añaden 'tareas' a la lista y se extraen en un bucle. Esto elimina el límite de recursión de Python y reduce la sobrecarga por marco, a costa de un código más complejo. El DFS iterativo con una pila explícita que vimos antes sigue exactamente este patrón.
# Recursive inorder traversal -> iterative with explicit stack
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def inorder_iterative(root):
result = []
stack = []
curr = root
while curr or stack:
while curr:
stack.append(curr)
curr = curr.left
curr = stack.pop()
result.append(curr.val)
curr = curr.right
return result
root = TreeNode(4, TreeNode(2, TreeNode(1), TreeNode(3)), TreeNode(6))
print(inorder_iterative(root)) # [1, 2, 3, 4, 6]Resumen: pila de llamadas y espacio
La pila de llamadas es la estructura de datos oculta que sustenta toda recursión. Su profundidad equivale a la complejidad espacial de su algoritmo recursivo. Python la limita a aproximadamente 1000 marcos, por lo que los algoritmos con una profundidad de recursión O(n) necesitan aumentar el límite (con riesgo) o reescribirse de forma iterativa. Al escribir código recursivo en entrevistas, indique siempre la complejidad espacial debida a la pila de llamadas: "Utiliza un espacio O(n) por la profundidad de la recursión" u "O(log n) para un recorrido de un árbol equilibrado".
Comprobación rápida
Ponga a prueba su comprensión de los conceptos de Data Structures & Algorithms — Coding Interview Prep de esta lección.
Resumen de la lección
En esta lección aprendió: cada llamada recursiva crea un marco de pila que contiene las variables locales y la dirección de retorno, la profundidad máxima de la pila equivale a la complejidad espacial auxiliar de la recursión, y el límite de recursión de Python (aproximadamente 1000) hace arriesgados los algoritmos con profundidad O(n) para valores grandes de n; conviértalos a soluciones iterativas mediante una pila explícita. A continuación, compararemos soluciones recursivas e iterativas y analizaremos cuándo utilizar cada una.
Preguntas frecuentes
¿La lección «Visualización de la pila de llamadas» es gratis?
Sí — el texto completo de «Visualización de la pila de llamadas» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Coding Interview Prep, actualiza a CoddyKit PRO. El curso de Coding Interview Prep incluye 4 lecciones en total.
¿Qué aprenderé en «Visualización de la pila de llamadas»?
Use el módulo sys de Python y trazas con print para observar cómo crecen y disminuyen los marcos de pila, y comprenda los riesgos de desbordamiento de pila en recursiones profundas. Practicas Coding Interview Prep con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Coding Interview Prep?
No se requiere experiencia previa. Coding Interview Prep en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.
¿Cuánto tiempo toma la lección «Visualización de la pila de llamadas»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Coding Interview Prep?
Sí. Cada lección de Coding Interview Prep incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Marco de recursión: caso base, confianza y construcción
- Visualización de la pila de llamadas
- Compensaciones entre recursión e iteración
- Memoización: almacenamiento en caché de resultados recursivos