¿Qué hace def contador_llamadas?

  PUNTOS CLAVE:

  1. funcion_original = saludar() (funciones que definimos)

  2. funcion_envoltura() ejecuta funcion_original() y retorna su resultado

  3. return funcion_envoltura retorna la función completa con su closure

  4. El closure mantiene vivo el contador entre llamadas

 EL CLOSURE ES: funcion_envoltura + contador

python
def contador_llamadas(funcion_original):
    contador = 0  # ← Parte del closure
    
    def funcion_envoltura():  # ← Parte del closure
        nonlocal contador
        contador += 1
        print(f"Llamada: {contador}")
        return funcion_original()
    
    return funcion_envoltura  # ← Retorna el CLOSURE completo

🔍 DESGLOSE:

El CLOSURE está formado por:

  1. funcion_envoltura - La función interna

  2. contador - La variable que recuerda

  3. funcion_original - La función original que se decoró

🎪 ANALOGÍA:

python
# El CLOSURE es como un "PAQUETE" que contiene:
┌─────────────────────────┐
│   📦 CLOSURE            │
│   • funcion_envoltura   │ ← El comportamiento
│   • contador = 3        │ ← La memoria  
│   • saludar()           │ ← La función original
└─────────────────────────┘

🎯 RESPUESTA DIRECTA:

El closure es funcion_envoltura CON SU CAPACIDAD DE RECORDAR contador y funcion_original.

No es solo la función, ni solo la variable... es el paquete completo que mantiene vivo el entorno donde fue creado.

CÓDIGO SIMPLIFICADO

python
def contador_llamadas(funcion_original):
    contador = 0
    
    def funcion_envoltura():
        nonlocal contador
        contador += 1
        print(f"Llamada número: {contador}")
        return funcion_original()  # ← Ejecuta saludar()
    
    return funcion_envoltura

# Definimos UNA sola función
def saludar():
    return "¡Hola!"

# Aplicamos el contador
saludar_contador = contador_llamadas(saludar)

# Usamos la función
print(saludar_contador())  # Llamada 1
print(saludar_contador())  # Llamada 2  
print(saludar_contador())  # Llamada 3

🎯 SALIDA:

text
Llamada número: 1
¡Hola!
Llamada número: 2
¡Hola!
Llamada número: 3
¡Hola!

🔍 VERSIÓN AÚN MÁS SIMPLE:

python
def contador_llamadas(funcion_original):
    contador = 0
    
    def funcion_envoltura():
        nonlocal contador
        contador += 1
        print(f"Veces que saludé: {contador}")
        return funcion_original()
    
    return funcion_envoltura

def saludar():
    return "¡Hola!"

# Más simple todavía
mi_saludo = contador_llamadas(saludar)

mi_saludo()  # → Veces que saludé: 1
mi_saludo()  # → Veces que saludé: 2  
mi_saludo()  # → Veces que saludé: 3

🎪 RESUMEN VISUAL:

python
# 1. Creamos el contador
mi_funcion = contador_llamadas(saludar)

# 2. Usamos - ¡el contador RECUERDA!
mi_funcion()  # → Llamada 1
mi_funcion()  # → Llamada 2
mi_funcion()  # → Llamada 3

¡Así de simple! La función mi_funcion recuerda cuántas veces se ha llamado, aunque contador_llamadas ya terminó de ejecutarse. 🎯

ha hora con el decorador

¡Ah, con el decorador! Así queda mucho más limpio:

📝 CÓDIGO CON DECORADOR

python
def contador_llamadas(funcion_original):
    contador = 0
    
    def funcion_envoltura():
        nonlocal contador
        contador += 1
        print(f"Llamada número: {contador}")
        return funcion_original()
    
    return funcion_envoltura

# Aplicamos el decorador
@contador_llamadas
def saludar():
    return "¡Hola!"

# ¡Listo! Ahora saludar YA TIENE el contador
print(saludar())  # Llamada 1
print(saludar())  # Llamada 2
print(saludar())  # Llamada 3

🎯 SALIDA:

text
Llamada número: 1
¡Hola!
Llamada número: 2
¡Hola!
Llamada número: 3
¡Hola!

🔍 ¿QUÉ HACE @contador_llamadas?

El decorador es solo azúcar sintáctica. Esto:

python
@contador_llamadas
def saludar():
    return "¡Hola!"

Es equivalente a esto:

python
def saludar():
    return "¡Hola!"

saludar = contador_llamadas(saludar)  # ← ¡El decorador hace esto por ti!

🎪 VERSIÓN MÁS LIMPIA:

python
def contador_llamadas(func):
    count = 0
    def wrapper():
        nonlocal count
        count += 1
        print(f"📞 Llamada #{count}")
        return func()
    return wrapper

@contador_llamadas
def saludar():
    return "¡Hola mundo!"

# Usamos la función decorada directamente
saludar()  # 📞 Llamada #1
saludar()  # 📞 Llamada #2  
saludar()  # 📞 Llamada #3

🎯 RESULTADO FINAL:

text
📞 Llamada #1
📞 Llamada #2
📞 Llamada #3

¡Así es como se usa en la práctica real! El decorador hace que la función original automáticamente tenga el comportamiento del contador. 

1. Definición del decorador:

python
def contador_llamadas(funcion_original):
    contador = 0  # ← Esta variable "recuerda" el estado
    
    def funcion_envoltura():
        nonlocal contador  # ← Permite modificar la variable exterior
        contador += 1
        print(f"📞 Esta es la llamada número: {contador}")
        return funcion_original()  # ← Ejecuta la función original
    
    return funcion_envoltura  # ← Retorna la función modificada
@contador_llamadas
def saludar():#funcion_original()
print("¡Hola mundo!")

¡Excelente! Ya casi lo tienes. Te lo explico super fácil:

🎯 LA CLAVE ESTÁ EN ESTA LÍNEA:

python
return funcion_envoltura  # ← ¡NO lleva paréntesis!

🔍 ¿QUÉ PASA PASO A PASO?

PASO 1: Cuando haces:

python
saludar_contador = contador_llamadas(saludar)
  • contador_llamadas crea contador = 0

  • contador_llamadas retorna la FUNCIÓN funcion_envoltura (sin ejecutarla)

  • saludar_contador ahora ES funcion_envoltura

PASO 2: Cuando ejecutas:

python
saludar_contador()
  • Estás ejecutando funcion_envoltura()

  • funcion_envoltura modifica contador (que sigue vivo)

  • funcion_envoltura ejecuta saludar()

🎪 ANALOGÍA FÁCIL:

Imagina que contador_llamadas es una fábrica de cajas mágicas:

python
def fabrica_cajas(funcion):
    contador = 0  # ← Dentro de la fábrica
    
    def caja_magica():
        nonlocal contador
        contador += 1
        print(f"Usos: {contador}")
        return funcion()
    
    return caja_magica  # ← Te doy la CAJA, no lo que hay dentro

# Recibo la caja mágica
mi_caja = fabrica_cajas(saludar)

# Cada vez que abro la caja:
mi_caja()  # ← La caja RECUERDA cuántas veces se abrió
mi_caja()  # ← Porque el contador vive DENTRO de la caja

🎯 RESPUESTA DIRECTA A TU PREGUNTA:

¿Cómo guarda el valor?

Porque return funcion_envoltura retorna la FUNCIÓN COMPLETA con su "mochila" (closure) donde guarda contador.

No retorna el resultado, retorna la función que puede recordar.

python
# Esto:
saludar_contador = contador_llamadas(saludar)

# Es como decir:
# "saludar_contador es una función que cada vez que la llames:
#   1. Incrementará un contador que recuerda
#   2. Ejecutará saludar()
# "

¡La magia está en que la función lleva consigo su memoria! 

Es una función que:

  1. Toma otra función como parámetro (funcion_original)

  2. Crea un contador que recuerda las llamadas

  3. Retorna una nueva función (funcion_envoltura) que envuelve a la original

Flujo paso a paso:

1. Definición del decorador:

python
def contador_llamadas(funcion_original):
    contador = 0  # ← Esta variable "recuerda" el estado
    
    def funcion_envoltura():
        nonlocal contador  # ← Permite modificar la variable exterior
        contador += 1
        print(f"📞 Esta es la llamada número: {contador}")
        return funcion_original()  # ← Ejecuta la función original
    
    return funcion_envoltura  # ← Retorna la función modificada

2. Aplicación del decorador:

python
@contador_llamadas
def saludar():
    print("¡Hola mundo!")

Esto es equivalente a:

python
def saludar():
    print("¡Hola mundo!")

saludar = contador_llamadas(saludar)  # ← ¡saludar ahora es funcion_envoltura!

3. Cuando llamas saludar():

Primera llamada:

text
📞 Esta es la llamada número: 1
¡Hola mundo!

Segunda llamada:

text
📞 Esta es la llamada número: 2  
¡Hola mundo!

El truco clave:

  • contador = 0 solo se ejecuta UNA vez cuando se aplica el decorador

  • funcion_envoltura mantiene acceso a contador gracias al closure

  • nonlocal permite modificar la variable del scope exterior

¿Por qué es útil?

  • Agrega funcionalidad sin modificar la función original

  • Mantiene estado entre llamadas

  • Reutilizable - puedes aplicarlo a cualquier función

Comentarios

Entradas populares de este blog

4 tipos de colecciones de datos más

¿Qué es un Closure?

Calculadora de edad