Menu

Decoradores en Python: @funciones, argumentos y functools.wraps

Qué son realmente los decoradores de Python, cómo escribir los tuyos y los patrones (argumentos, apilado, wraps) que los hacen valer la pena.

Un decorador es una función que envuelve una función

Esa frase suena abstracta, pero la mecánica es simple. Un decorador toma una función como entrada, devuelve una función como salida. La función que devuelve normalmente llama a la original, con algún comportamiento extra envuelto alrededor de la llamada.

El ejemplo más corto posible:

main.py
Output
Click Run to see the output here.

shout es el decorador. Toma una función (greet), construye una nueva función (wrapper) que llama a la original y pone el resultado en mayúsculas, y la devuelve. Reasignar greet = shout(greet) cambia la original por la versión envuelta.

Ese patrón de reasignación es tan común que Python le dio una sintaxis dedicada.

El @ es azúcar para una reasignación

@name en la línea de arriba de un def es equivalente a name = name(...) justo después de que la función se defina:

main.py
Output
Click Run to see the output here.

@shout se lee como "aplica el decorador shout a esta función". Python ejecuta greet = shout(greet) inmediatamente después del def — misma mecánica que antes, menos escritura.

Cuando veas @name, reemplázalo mentalmente por function = name(function). Eso es todo lo que significa la sintaxis.

Manejar argumentos

La mayoría de funciones toman argumentos. Un decorador útil los pasa a través. El idiomático es *args, **kwargs — la forma de Python de aceptar cualquier argumento — porque al wrapper no debería importarle qué espera la función envuelta:

main.py
Output
Click Run to see the output here.

*args captura todos los argumentos posicionales. **kwargs captura todos los argumentos por palabra clave. El wrapper reenvía todo a la función envuelta sin cambios, y luego hace el trabajo extra para el que existe el decorador — aquí, poner el resultado en mayúsculas.

Esta es la forma que toman la mayoría de decoradores reales.

Un ejemplo más útil: timing

Imprimir cuánto tarda una función:

main.py
Output
Click Run to see the output here.

El patrón — ejecutar algo antes de la llamada, ejecutar algo después — es lo que terminan haciendo la mayoría de decoradores. Logging, comprobaciones de auth, reintentos y validación de entrada siguen todos la misma forma.

Preservar la identidad original: functools.wraps

Decorar una función la reemplaza, lo que significa que la función envuelta pierde sus atributos originales __name__ y __doc__:

main.py
Output
Click Run to see the output here.

greet.__name__ ahora es "wrapper" y la docstring ha desaparecido. Eso rompe help(), los tracebacks y cualquier herramienta que inspeccione la función.

El arreglo es una línea: @functools.wraps(func) en la función interna copia los metadatos.

main.py
Output
Click Run to see the output here.

Añade siempre @wraps(func) a la función interna. No cuesta nada y evita sesiones de depuración sorprendentes después.

Decoradores con argumentos

A veces el propio decorador necesita configuración — "reintenta esta función hasta 3 veces", "logea a nivel DEBUG". Eso significa una capa más de anidado: una función externa que toma los argumentos y devuelve un decorador.

main.py
Output
Click Run to see the output here.

Tres capas suena a mucho. Léelo de fuera hacia dentro:

  1. repeat(times=3) es una llamada a función. Devuelve el decorator.
  2. decorator es el decorador real — toma una función y devuelve una envuelta.
  3. wrapper es la función envuelta que se ejecuta en tiempo de llamada.

Esta forma impulsa @retry(times=5), @cache(maxsize=100) y decoradores de framework como @app.route("/users"). Una vez que ves el patrón de tres capas, toda la familia se lee de la misma forma.

Apilar decoradores

Puedes aplicar más de un decorador a una función. Se apilan de abajo arriba — el más cercano al def se ejecuta primero:

main.py
Output
Click Run to see the output here.

add_exclaim envuelve primero, añadiendo el !. Luego shout envuelve eso, poniendo todo en mayúsculas. La salida es HI, ROSA!.

El orden importa. Invertir el apilado te da HI, ROSA! con la exclamación añadida después de las mayúsculas — visualmente idéntico aquí, pero imagina un decorador que formatea JSON: ejecutarlo antes o después de un decorador que logea la entrada puede producir resultados muy distintos.

Decoradores incorporados que verás

Python y su librería estándar vienen con un puñado de decoradores que te encontrarás en código real:

main.py
Output
Click Run to see the output here.
  • @property convierte un método en un atributo computado.
  • @staticmethod marca un método que no usa self ni cls.
  • @classmethod recibe la clase como cls en vez de una instancia — genial para constructores alternativos.
  • @functools.lru_cache memoiza resultados, así que llamadas repetidas con los mismos argumentos golpean una caché.

Los decoradores de framework (@app.route, @pytest.fixture, @dataclass) siguen la misma maquinaria. Nada especial — solo funciones que envuelven funciones.

Cuándo escribir uno, cuándo no

Escribe un decorador cuando quieras aplicar el mismo comportamiento a muchas funciones — timing, logging, reintentos, comprobaciones de autorización. El punto entero es que el comportamiento se queda fuera del cuerpo de la función.

Sáltate el decorador cuando:

  • El comportamiento pertenece a una función específica. Ponlo en la función.
  • Solo lo quieres para tests. Un fixture o un parámetro es más claro.
  • Te tienta apilar cuatro o cinco. En ese punto, el flujo de control queda oculto en la cadena de decoradores — un lector tiene que desenrollar cada capa para ver qué se ejecuta realmente. Una función helper directa se puede leer mejor.

Los decoradores son una herramienta afilada. Bien usados, mantienen el código DRY y la intención obvia. Mal usados, ocultan lo que hace el programa. Inclínate hacia "obvio" cuando estés decidiendo si recurrir a uno.

Siguiente: type hints

Los decoradores son un sitio común donde encontrarte con type hints en la naturaleza — las funciones wrapper a menudo anotan sus firmas. Los type hints son una feature pequeña que se paga rápido, y vienen ahora.

Preguntas frecuentes

¿Qué es un decorador en Python?

Un decorador es una función que toma otra función y devuelve una nueva función — normalmente una que envuelve la original con comportamiento extra. Aplicas uno con @decorator_name en la línea de arriba de un def. La sintaxis @ es un atajo para func = decorator_name(func).

¿Para qué se usan los decoradores en Python?

Para añadir comportamiento alrededor de una función sin editar su cuerpo — logging, timing, caché, comprobaciones de autenticación, validación de entrada, reintentos. Los frameworks los usan mucho: @app.route(...) en Flask, @pytest.fixture en pytest, @property y @staticmethod incorporados.

¿Puedo escribir mi propio decorador?

Sí. Un decorador es simplemente una función que toma una función y devuelve una función. La mayoría de decoradores personalizados envuelven la llamada original en una pequeña función interna que hace algo antes, después o alrededor. Usa functools.wraps en la función interna para preservar el nombre y docstring originales.

Aprende a programar con Coddy

COMENZAR