¿Qué es un Closure?
nonlocal permite que una función interna acceda y modifique variables de una función externa (pero no globales). Sin nonlocal, la variable sería de solo lectura.
Tu código como ejemplo:
def contador_llamadas(funcion_original):
contador = 0 # ← Variable local de contador_llamadas
def funcion_envoltura():
nonlocal contador # ← ¡Aquí está el closure!¡Aquí referenciamos la variable externa!
contador += 1 # ← Sin nonlocal, esto causaría error
print(f"📞 Llamada número: {contador}")
return funcion_original()
return funcion_envoltura # ← Retornamos la función, NO el valorLa función que "recuerda" su entorno
La función
funcion_envolturaes el closure - la que recuerda su entorno.Veámoslo en acción:
@contador_llamadas def saludar(): return "¡Hola!" # Uso: mensaje1 = saludar() # 📞 Llamada número: 1 mensaje2 = saludar() # 📞 Llamada número: 2 mensaje3 = saludar() # 📞 Llamada número: 3¿Qué está pasando realmente?
Cuando se crea
funcion_envoltura, captura (recuerda) la variablecontadorde su entornoAunque
contador_llamadasya terminó de ejecutarse,funcion_envolturamantiene acceso acontadorCada vez que llamamos a
funcion_envoltura, modifica esa misma variablecontadorSin
nonlocalno funcionaría:funcion_envoltura() retorna lo que retorne funcion_original():def contador_llamadas_mal(funcion_original): contador = 0 def funcion_envoltura(): # Sin nonlocal - ¡ERROR! contador += 1 # UnboundLocalError return funcion_original()return funcion_envoltura # ← Retornamos la función CON su closureResumen clave:
nonlocal: Permite modificar variables de funciones externasClosure:
funcion_envolturaes el closure - recuerdacontadoryfuncion_originalPersistencia: El entorno (variables
contadoryfuncion_original) sobrevive incluso después quecontador_llamadasterminó
1. contador_llamadas() (función exterior)
def contador_llamadas(funcion_original):
# ...
return funcion_envoltura # ← ¡Esto es lo importante!
Retorna: La función funcion_envoltura (sin ejecutarla)
def contador_llamadas(funcion_original): # ... return funcion_envoltura # ← ¡Esto es lo importante!
funcion_envoltura (sin ejecutarla)2. funcion_envoltura() (función interior)
def funcion_envoltura():
# ...
return funcion_original() # ← ¡Esto es diferente!
Retorna: El resultado de ejecutar funcion_original()
def funcion_envoltura(): # ... return funcion_original() # ← ¡Esto es diferente!
funcion_original()Ejemplo práctico para entenderlo:
def mi_funcion():
return "Hola mundo"
# contador_llamadas retorna una función
funcion_decorada = contador_llamadas(mi_funcion)
print(type(funcion_decorada)) # <class 'function'>
# Ahora ejecutamos la función retornada
resultado = funcion_decorada()
# 📞 Llamada número: 1
print(resultado) # "Hola mundo"
def mi_funcion(): return "Hola mundo" # contador_llamadas retorna una función funcion_decorada = contador_llamadas(mi_funcion) print(type(funcion_decorada)) # <class 'function'> # Ahora ejecutamos la función retornada resultado = funcion_decorada() # 📞 Llamada número: 1 print(resultado) # "Hola mundo"
Visualización del flujo:
contador_llamadas(mi_funcion)
↓
RETORNA: funcion_envoltura (con closure)
↓
funcion_envoltura()
↓
RETORNA: mi_funcion() → "Hola mundo"
contador_llamadas(mi_funcion)
↓
RETORNA: funcion_envoltura (con closure)
↓
funcion_envoltura()
↓
RETORNA: mi_funcion() → "Hola mundo"¿Por qué es útil?
Esto permite crear decoradores:
@contador_llamadas
def saludar():
return "¡Hola!"
# Equivale a: saludar = contador_llamadas(saludar)
En resumen:
contador_llamadas() retorna una función
funcion_envoltura() retorna lo que sea que retorne la función original
@contador_llamadas def saludar(): return "¡Hola!" # Equivale a: saludar = contador_llamadas(saludar)
contador_llamadas() retorna una función
funcion_envoltura() retorna lo que sea que retorne la función original
¿Qué pasa aquí?
Cuando ejecutas
@contador_llamadas, la funcióncontador_llamadastermina y su scope local "debería" destruirsePERO
funcion_envolturamantiene una "referencia" a las variables que necesita (contadoryfuncion_original)Esto es el closure: la función interna "encierra" y recuerda las variables del scope exterior
Ejemplo más simple:
def crear_contador():
cuenta = 0 # Variable local
def incrementar():
nonlocal cuenta
cuenta += 1
return cuenta
return incrementar # ← Retornamos la función, no el valor
# Uso:
mi_contador = crear_contador()
print(mi_contador()) # 1
print(mi_contador()) # 2
print(mi_contador()) # 3
otro_contador = crear_contador() # ← ¡Nuevo closure independiente!
print(otro_contador()) # 1 (empieza desde 0 otra vez)¿Por qué es mágico?
crear_contador()ya terminó de ejecutarsecuentadebería haberse destruido (era variable local)PERO
mi_contadortodavía recuerdacuentay puede modificarla
Características clave:
Persistencia de estado: Las variables "viven" mientras la función viva
Encapsulación: El estado es privado, no accesible desde fuera
Múltiples instancias: Cada closure es independiente
Sin closure no funcionaría:
# Esto NO funcionaría sin closure:
def contador_sin_closure():
return contador + 1 # ← Error: contador no existe aquí¿Dónde se usan los closures?
Decoradores (como tu ejemplo)
Callbacks y event handlers
Funciones de fábrica que crean funciones personalizadas
Programación funcional
Visualización del Flujo del Closure
Te muestro paso a paso dónde ocurre la "magia" de la persistencia:
📍 PASO 1: Creación del Closure
def contador_llamadas(funcion_original):
contador = 0 # 🎯 ESTA variable es la "mágica"
def funcion_envoltura():
nonlocal contador # ← ¡CAPTURA la variable!
contador += 1
print(f"📞 Llamada número: {contador}")
return funcion_original()
return funcion_envoltura # ← Retorna la función CON su entornoMEMORIA en este momento:
┌─────────────────────────────────┐
│ Función contador_llamadas │
│ - Variable: contador = 0 │ ← 🎯 ESTA se preserva
└─────────────────────────────────┘
↑
│ Capturada por closure
┌─────────────────────────────────┐
│ Función funcion_envoltura │
│ - __closure__: [contador] │ ← ¡Lleva consigo la variable!
└─────────────────────────────────┘📍 PASO 2: Ejecución y "Finalización"
# contador_llamadas TERMINA de ejecutarse
funcion_decorada = contador_llamadas(saludar)
# ↑ ¡Pero la variable contador SOBREVIVE!ANTES: DESPUÉS (la magia):
┌─────────────────────────┐ ┌─────────────────────────┐
│ Ámbito contador_llamadas│ │ contador_llamadas YA │
│ contador = 0 │ │ NO EXISTE en ejecución │
│ funcion_original = ... │ │ pero... │
└─────────────────────────┘ └─────────────────────────┘
↑
│ ¡LA VARIABLE SOBREVIVE!
┌─────────────────────────┐ ┌─────────────────────────┐
│ funcion_envoltura │ │ funcion_decorada │
│ │ │ - __closure__: │
│ │ │ [cell(contador=0), │ ← 🎯 ¡AQUÍ ESTÁ LA MAGIA!
│ │ │ cell(funcion_original)]│
└─────────────────────────┘ └─────────────────────────┘📍 PASO 3: Las Llamadas Múltiples (¡Donde se ve la magia!)
# PRIMERA llamada
resultado1 = funcion_decorada()
# 📞 Llamada número: 1
# ↑ ¡contador ahora vale 1 en el closure!
# SEGUNDA llamada
resultado2 = funcion_decorada()
# 📞 Llamada número: 2
# ↑ ¡contador ahora vale 2 en el closure!
# TERCERA llamada
resultado3 = funcion_decorada()
# 📞 Llamada número: 3
# ↑ ¡contador ahora vale 3 en el closure!🎯 EL PUNTO EXACTO DE LA MAGIA:
def contador_llamadas(funcion_original):
contador = 0 # 🎯 ¡ESTA variable SOBREVIVE aquí!
# ↑ Aunque contador_llamadas TERMINÓ, esta variable
# vive en el __closure__ de funcion_envoltura
def funcion_envoltura():
nonlocal contador # ← Referencia a la variable "inmortal"
contador += 1 # ← Modifica la variable que debería haber muerto
return funcion_original()
return funcion_envoltura # ← Retorna la función + variable capturada🔍 Para ver la magia en acción:
# Verificar el closure
print(funcion_decorada.__closure__) # → (<cell at ...>,)
print(funcion_decorada.__closure__[0].cell_contents) # → 3 (el valor actual!)
# Demostración visual
def visualizar_magia():
@contador_llamadas
def ejemplo():
return "¡Funciono!"
# contador_llamadas YA terminó aquí...
# ¡Pero el closure mantiene vivo el contador!
ejemplo() # Llamada 1
ejemplo() # Llamada 2
ejemplo() # Llamada 3
print(f"El closure guarda: {ejemplo.__closure__[0].cell_contents}")
# → El closure guarda: 3
visualizar_magia()¡La magia está en que contador vive en el __closure__ de la función incluso después de que su función padre haya terminado!
Comentarios
Publicar un comentario