PythonAprende PythonDocumentación

Decoradores

Funciones que envuelven funciones para extender su comportamiento.

Un decorador es una función que recibe una función y devuelve otra función extendida. Te agrega comportamiento —logging, caché, validación— sin modificar el código original.

La idea en crudo

def registro(func):
    def wrapper():
        print("→ entrando a", func.__name__)
        resultado = func()
        print("← saliendo de", func.__name__)
        return resultado
    return wrapper

def trabajar():
    print("trabajando...")

trabajar = registro(trabajar)   # "envolver" a mano
trabajar()
# → entrando a trabajar
# trabajando...
# ← saliendo de trabajar

La sintaxis @: azúcar del lenguaje

@registro
def trabajar():
    print("trabajando...")

@registro encima de def es exactamente equivalente a trabajar = registro(trabajar). Solo azúcar — pero azúcar que se lee genial.

Decoradores con argumentos (el patrón completo)

La función original puede recibir parámetros; el wrapper los reenvía con *args, **kwargs:

def registro(func):
    def wrapper(*args, **kwargs):
        print(f"→ {func.__name__}({args})")
        resultado = func(*args, **kwargs)
        print(f"← {func.__name__} devolvió {resultado}")
        return resultado
    return wrapper

@registro
def sumar(a, b):
    return a + b

sumar(2, 3)
# → sumar((2, 3))
# ← sumar devolvió 5

Este es el 90% de los decoradores que verás: wrapper(*args, **kwargs) reenvía todo.

functools.wraps: el detalle profesional

El wrapper "se queda" con la identidad de la función original:

import functools

def registro(func):
    @functools.wraps(func)        # ← preserva nombre y docstring
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@registro
def sumar(a, b):
    """Suma dos números."""
    return a + b

print(sumar.__name__)    # sumar (sin wraps sería "wrapper")
print(sumar.__doc__)     # Suma dos números.

Sin @functools.wraps, el debugging y la introspección se rompen. Hábito: siempre inclúyelo.

Decoradores que reciben parámetros

Un nivel más de envoltura — una función que fabrica el decorador:

def repetir(veces):
    def decorador(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            resultado = None
            for _ in range(veces):
                resultado = func(*args, **kwargs)
            return resultado
        return wrapper
    return decorador

@repetir(veces=3)
def saludar():
    print("¡hola!")

saludar()   # ¡hola! x3

Casos de uso reales

@app.route("/usuarios")        # Flask/FastAPI: registrar rutas
def listar_usuarios(): ...

@login_requerido               # autenticación
def panel(): ...

@cache                         # functools: cachear resultados
def calculo_costoso(): ...

Los frameworks de Python están construidos sobre decoradores. Dominarlos es leer mejor el ecosistema entero.

Resumen

  • Decorador = función que envuelve otra para extenderla
  • @decoradorfunc = decorador(func)
  • wrapper(*args, **kwargs) reenvía los argumentos; devuelve el resultado
  • @functools.wraps(func) preserva la identidad de la original
  • Para decoradores con opciones: una función fabrica el decorador

Quiz

  1. 1. ¿Qué es un decorador?

  2. 2. ¿A qué equivale escribir @registro encima de def hola(): ...?

  3. 3. ¿Para qué se usa @functools.wraps(func) en el wrapper de un decorador?

Ejercicios

Ejercicio 1: Duplicar el resultado

Crea el decorador `duplicar` que devuelva un wrapper que llama a la función original y devuelve su resultado multiplicado por 2. Aplícalo con @duplicar a sumar(a, b).

Cargando editor…

Ejercicio 2: Registro antes y después

Crea el decorador `registro` que imprima ANTES antes de ejecutar la función y DESPUÉS al terminar. Aplícalo a una función que imprime trabajo.

Cargando editor…