Aprende leyendo en orden

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)».

@ significa «ejecuta esta función a través de aquella función»
@loggerdef greet():greet = logger(greet)se expande a
Escribir @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
Cómo el decorador logger envuelve una función
Módulo (espacio de nombres global)
  • greet se reemplaza por la función wrapper
  • El cuerpo original de greet sigue vivo como func dentro de wrapper
Marco de logger(func)
  • func mantiene la greet original
  • Construye wrapper dentro y la retorna
wrapper (una clausura que recuerda func)
  • Ejecuta pre → func() → post en orden
  • Desde fuera, esta es la nueva greet
Lo que hace @logger es intercambiar la greet global por una función diferente llamada wrapper. El cuerpo original de greet se llama como func desde dentro de wrapper.

Construye un decorador bracket que imprima saludos antes y después de una función y aplícalo en capa.

① Define def bracket(func):, y dentro def wrapper():. Haz que wrapper ejecute print("--- inicio ---")func()print("--- fin ---") en orden, y haz que el externo retorne wrapper.

② Define def introduce(): decorada con @bracket, con solo print("Soy Ana") en el cuerpo.

③ Llama a introduce() y confirma que el cuerpo queda en sándwich entre --- inicio --- / --- fin ---.

(Cuando la respuesta sea correcta, aparecerá la explicación.)

Editor Python

Ejecutar el código para ver el resultado

Pasar cualquier argumento con *args / **kwargs

Hasta ahora, wrapper no tomaba argumentos. Cuando quieras decorar funciones que 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
Cómo *args / **kwargs pasan los argumentos directamente
add(2, b=3)wrapper(*args, **kwargs)args=(2,), kwargs={'b': 3}desempaqueta y reenvíacomo func(*args, **kwargs)add(a, b)add original (a=2, b=3)retorna 5wrapper lo retornatal cualrecibepaso previodesempaquetacalculaenvía resultado de vuelta

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.

Construye un decorador log_call que imprima cada llamada y aplícalo a una función de 2 argumentos.

① Define def log_call(func):, y dentro def wrapper(*args, **kwargs):.

② En wrapper, imprime f"llamada: args={args}, kwargs={kwargs}", luego result = func(*args, **kwargs), después return result.

③ Haz que el externo retorne wrapper.

④ Define def multiply(a, b): return a * b decorada con @log_call. Llama a print(multiply(4, 5)) y print(multiply(2, b=10)) y confirma que aparece el log seguido del valor de retorno.

Editor Python

Ejecutar el código para ver el resultado
QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1¿Cuál de las siguientes tiene el mismo significado que poner @logger encima de def greet(): ...?

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))