Los decoradores en Python surgieron históricamente como una forma de hacer el código más compacto y legible, encapsulando comportamientos repetidos alrededor de funciones y métodos. Antes de la llegada de la sintaxis @decorator, se aplicaban de manera explícita, lo que complicaba la comprensión del código. Hoy en día, los decoradores juegan un papel clave en la organización de la lógica, verificaciones repetidas, registro, almacenamiento en caché y más.
El problema al trabajar con decoradores radica en manejar correctamente las diferencias entre funciones, métodos de instancia y métodos estáticos/clase. A menudo surgen errores relacionados con la pérdida de información sobre el método, el enlace tardío/temprano de self, así como con la firma de la función (signature).
La solución es que al escribir decoradores universales se debe usar el módulo functools.wraps para conservar los metadatos, y también tener cuidado con el tipo de objeto decorado (por ejemplo, considerar correctamente que los métodos de instancia reciben self como el primer argumento).
Ejemplo de código:
import functools def my_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): print(f"Antes de la función: {func.__name__}") result = func(*args, **kwargs) print(f"Después de la función: {func.__name__}") return result return wrapper class Example: @my_decorator def method(self, x): print(f"Método llamado con {x}") ex = Example() ex.method(5)
Características clave:
self como el primer argumento.functools.wraps para preservar los metadatos originales.Si se utiliza un decorador escrito para una función en un método de clase, ¿es obligatorio usar functools.wraps, y qué pasará con self?
No, el decorador en sí funciona, pero sin wraps se pierde el nombre de la función, la asistencia del IDE y la documentación. Self seguirá siendo el primer parámetro, pero la pérdida de metadatos dificultará la depuración y la reflexión.
def bad_decorator(f): def wrapper(*args, **kwargs): print("decorado") return f(*args, **kwargs) return wrapper class Test: @bad_decorator def foo(self): pass print(Test().foo.__name__) # wrapper
¿Se puede aplicar el mismo decorador tanto a un método de instancia como a un método estático?
Sí, pero hay que recordar: los métodos estáticos no reciben self como primer argumento. Si el decorador espera trabajar con self, se generarán errores con @staticmethod / @classmethod.
¿Cómo afecta el decorador a la firma del método y la autocompletación?
En resumen: sin functools.wraps, se pierde la firma y la docstring, lo que provoca que IDEs y muchas herramientas de sugerencia dejen de funcionar correctamente.
functools.wraps — pérdida del nombre de la función, docstring, dificultando la depuración.self — no funciona correctamente en los métodos.Caso negativo: Decoradores sin functools.wraps en todos los métodos de la clase.
Ventajas: prototipo rápido, funciona.
Desventajas: no se pueden buscar errores a través de la pila, el IDE no sugiere la firma.
Caso positivo: Los decoradores utilizan functools.wraps, el código está documentado.
Ventajas: legibilidad, mantenimiento, comodidad en el IDE.
Desventajas: mínima adición a la sintaxis y la atención.