Los decoradores en Python son una de las características más elegantes y potentes del lenguaje. Permiten modificar o extender el comportamiento de funciones y clases sin alterar su código. Frameworks como Flask, FastAPI y Django los usan de forma masiva. En esta guía aprenderás qué son, cómo funcionan y cómo crear tus propios decoradores Python con ejemplos reales.

¿Qué es un decorador en Python?

Un decorador es una función que recibe otra función como argumento, añade funcionalidad adicional y devuelve una nueva función. La sintaxis @nombre_decorador es azúcar sintáctica: aplicar @mi_decorador sobre una función es equivalente a escribir funcion = mi_decorador(funcion).

def mi_decorador(func):
    def wrapper(*args, **kwargs):
        print("Antes de ejecutar")
        resultado = func(*args, **kwargs)
        print("Despues de ejecutar")
        return resultado
    return wrapper

@mi_decorador
def saludar(nombre):
    print(f"Hola, {nombre}!")

saludar("Python")
# Antes de ejecutar
# Hola, Python!
# Despues de ejecutar

Decorador de logging

import functools
import logging

logging.basicConfig(level=logging.INFO)

def log_llamada(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        logging.info(f"Llamando a {func.__name__} con args={args}, kwargs={kwargs}")
        resultado = func(*args, **kwargs)
        logging.info(f"{func.__name__} devolvio {resultado}")
        return resultado
    return wrapper

@log_llamada
def dividir(a, b):
    return a / b

dividir(10, 2)  # INFO: Llamando a dividir con args=(10, 2)...

Decorador de caché manual

def cache(func):
    almacen = {}
    @functools.wraps(func)
    def wrapper(*args):
        if args not in almacen:
            almacen[args] = func(*args)
        return almacen[args]
    return wrapper

@cache
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(40))  # Rapido gracias a la cache

Decorador con argumentos

Para que un decorador acepte sus propios argumentos, necesitas una capa extra de anidamiento.

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

@repetir(3)
def saludar():
    print("Hola!")

saludar()  # Imprime "Hola!" tres veces

Decorador de validación

def validar_positivos(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        for arg in args:
            if isinstance(arg, (int, float)) and arg < 0:
                raise ValueError(f"Argumento negativo no permitido: {arg}")
        return func(*args, **kwargs)
    return wrapper

@validar_positivos
def raiz_cuadrada(n):
    return n ** 0.5

print(raiz_cuadrada(16))  # 4.0
# raiz_cuadrada(-4)  # ValueError

Decorador de clase — Singleton

def singleton(cls):
    instancias = {}
    @functools.wraps(cls)
    def obtener_instancia(*args, **kwargs):
        if cls not in instancias:
            instancias[cls] = cls(*args, **kwargs)
        return instancias[cls]
    return obtener_instancia

@singleton
class Configuracion:
    def __init__(self):
        self.modo = "produccion"

c1 = Configuracion()
c2 = Configuracion()
print(c1 is c2)  # True — misma instancia

Decoradores estándar de Python

  • @property — convierte un método en atributo de solo lectura
  • @classmethod — método que recibe la clase como primer argumento
  • @staticmethod — método sin acceso a instancia ni clase
  • @functools.lru_cache — caché automática con límite configurable
  • @dataclasses.dataclass — genera __init__, __repr__ y __eq__ automáticamente

Preguntas frecuentes sobre decoradores en Python

¿Qué es exactamente un decorador en Python?

Un decorador es una función que toma otra función como argumento, añade funcionalidad y devuelve una nueva función. La sintaxis @nombre_decorador es azúcar sintáctica: escribir @mi_decorador sobre def funcion() equivale a funcion = mi_decorador(funcion). Son la base de frameworks como Flask (rutas con @app.route), FastAPI y Django REST Framework.

¿Para qué sirve @functools.wraps?

@functools.wraps preserva los metadatos de la función original (nombre __name__, docstring __doc__ y firma) cuando creas un wrapper. Sin él, la función decorada pierde su identidad, lo que complica el debugging y la introspección. Siempre debes añadir @functools.wraps(func) justo antes de definir la función wrapper dentro de tu decorador.

¿Cómo se aplican múltiples decoradores a una función?

Se apilan verticalmente y se aplican de abajo hacia arriba. Si escribes @decorador_b encima de @decorador_a sobre una función, primero se aplica decorador_a y luego decorador_b sobre el resultado. El orden importa: por ejemplo, poner @log antes de @validar registrará incluso las llamadas con argumentos inválidos antes de que falle la validación.

¿Cuál es la diferencia entre un decorador de función y uno de clase?

Un decorador de función envuelve funciones individuales añadiéndoles comportamiento. Un decorador de clase recibe una clase entera y puede modificar sus atributos, añadir métodos o devolver una clase diferente. Ejemplos en la librería estándar: @dataclass añade __init__ y __repr__ automáticamente, y el patrón @singleton garantiza que solo exista una instancia de la clase.

Conclusión

Los decoradores hacen el código Python más limpio y reutilizable. Sigue aprendiendo con nuestra guía de POO en Python, los ejercicios con funciones y el tutorial de FastAPI donde los decoradores son protagonistas.