Pregunta 1¿Cuál de las siguientes tiene el mismo significado que poner @logger encima de def greet(): ...?
Decoradores — Añadir comportamiento a las funciones con @
Aprende los decoradores de Python desde cero: la sintaxis @, def → return inner y decoradores con argumentos.
Al final de el artículo anterior sobre lambdas, habías visto las principales formas de tratar las funciones como valores. Para cerrar, fijemos los decoradores — la sintaxis dedicada a apilar comportamiento extra encima de una función.
¿Qué es un decorador?
Un decorador es una forma de añadir comportamiento antes y después de una función sin cambiar la función misma. Cosas como «registrar la llamada», «medir el tiempo de ejecución» o «cachear el resultado» — comportamiento compartido que quieres apilar sobre muchas funciones — pueden vivir en un único sitio.
La sintaxis es solo una línea encima de la definición de la función: @nombre_decorador. Python lo lee como «lo mismo que func = nombre_decorador(func)».
@logger encima de def greet(): hace que Python ejecute greet = logger(greet) internamente, reemplazando greet con una función nueva envuelta por logger.# El decorador en sí (una función de orden superior que toma y retorna una función)
def logger(func):
def wrapper():
print("=== inicio ===")
func() # llama a la función original
print("=== fin ===")
return wrapper
# Lado del que llama: solo añade @
@logger
def greet(): # → logger(greet)
print("Hola")
greet()
# === inicio ===
# Hola
# === fin ===
# Equivalente internamente a:
# def greet():
# print("Hola")
# greet = logger(greet)
El decorador básico — Envolver una función con wrapper
El esqueleto de un decorador es una forma de 3 pasos: la función externa toma func, la función interna (por convención wrapper) llama a func(), y haces return wrapper. El wrapper que mantiene func disponible mientras se ejecuta es exactamente una clausura.
Lo que escribas antes y después de func() se ejecuta cada vez que se llama a la función decorada.
def logger(func):
def wrapper(*args, **kwargs):
print(f"[LOG] ejecutando {func.__name__}")
result = func(*args, **kwargs)
print(f"[LOG] {func.__name__} terminado")
return result
return wrapper
@logger
def greet(name):
return f"Hola, {name}"
print(greet("Ana"))
# [LOG] ejecutando greet
# [LOG] greet terminado
# Hola, Ana
- greet se reemplaza por la función wrapper
- El cuerpo original de
greetsigue vivo comofuncdentro dewrapper
funcmantiene lagreetoriginal- Construye
wrapperdentro y la retorna
- Ejecuta pre →
func()→ post en orden - Desde fuera, esta es la nueva
greet
Pasar cualquier argumento con *args / **kwargs
Hasta ahora, wrapper no tomaba argumentos. Cuando quieras decorar funciones que sí toman argumentos, usa *args / **kwargs para aceptar cualquier argumento tal cual y reenviarlos directos a `func`.
Eso convierte al decorador en uno genérico que funciona con cualquier firma de función. Maneja add(2, 3) (posicional) y add(2, 3, name="ABC") (por palabra clave) con el mismo decorador.
def log_call(func):
def wrapper(*args, **kwargs):
print(f"llamada: args={args}, kwargs={kwargs}")
result = func(*args, **kwargs) # desempaqueta y reenvía aquí también
print(f"resultado: {result}")
return result # no olvides retornarlo
return wrapper
@log_call
def add(a, b):
return a + b
print(add(2, 3))
# llamada: args=(2, 3), kwargs={}
# resultado: 5
# 5
print(add(2, b=3))
# llamada: args=(2,), kwargs={'b': 3}
# resultado: 5
# 5
No olvides retornar el resultado
Si wrapper solo escribe result = func(...) y se olvida del return, el valor de retorno de la función decorada se convierte silenciosamente en None. El accidente clásico es encontrar que add(2, 3) retorna None calladamente — cuando escribas un decorador, trata return result como parte de la misma memoria muscular.
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2¿Qué imprime este código?def deco(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs) * 2
return wrapper
@deco
def plus(a, b):
return a + b
print(plus(3, 4))