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:
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:
@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:
*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:
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__:
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.
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.
Tres capas suena a mucho. Léelo de fuera hacia dentro:
repeat(times=3)es una llamada a función. Devuelve eldecorator.decoratores el decorador real — toma una función y devuelve una envuelta.wrapperes 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:
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:
@propertyconvierte un método en un atributo computado.@staticmethodmarca un método que no usaselfnicls.@classmethodrecibe la clase comoclsen vez de una instancia — genial para constructores alternativos.@functools.lru_cachememoiza 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.