2.23. Decoradores¶
Un decorador es una porción de sintaxis que envuelve una función (o un método) dentro de otra función. El envoltorio puede añadir comportamiento antes y después de la llamada original, sustituir el valor de retorno o adjuntar metadatos. La forma es:
@wrapper
def f():
...
La línea @wrapper equivale a f = wrapper(f): la función f se construye con normalidad, luego se le entrega a wrapper, y el resultado se vuelve a vincular al nombre f.
Un decorador recibe una función y devuelve una nueva función.¶
2.23.1. Decoradores de métodos integrados¶
Algunos decoradores vienen con Python y se usan dentro de los cuerpos de clase.
2.23.1.1. @property¶
Convierte un método en un atributo calculado. Quien llama accede a él como si fuera un atributo corriente (sin paréntesis), pero un método se ejecuta en cada lectura:
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return 3.14159 * self.radius * self.radius
c = Circle(5)
print(c.area) # 78.53975 -- no parentheses
Úsalo cuando un atributo que parece sencillo necesite un pequeño cálculo por detrás. Si el trabajo es costoso, prefiere un método normal: quien llama no espera que las lecturas de atributos sean lentas.
2.23.1.2. @classmethod¶
Define un método que recibe la clase como primer argumento en lugar de una instancia. El primer parámetro se nombra convencionalmente cls:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
@classmethod
def origin(cls):
return cls(0, 0)
p = Point.origin()
Los métodos de clase son la forma estándar de proporcionar constructores alternativos: funciones de fábrica que devuelven una instancia construida de una manera no predeterminada.
2.23.1.3. @staticmethod¶
Define un método que no recibe ni la instancia ni la clase: es simplemente una función corriente que reside en el espacio de nombres de la clase por motivos organizativos:
class Temperature:
@staticmethod
def c_to_f(c):
return c * 9 / 5 + 32
Temperature.c_to_f(100) # 212.0
Úsalo con moderación; si una función realmente no tiene nada que ver con el estado de la clase, una función corriente a nivel de módulo suele ser más limpia.
2.23.2. Escribir un decorador personalizado¶
Un decorador es una función que recibe una función y devuelve una función. La forma mínima:
def log_calls(func):
def wrapper(*args, **kwargs):
print("calling", func.__name__)
return func(*args, **kwargs)
return wrapper
@log_calls
def add(a, b):
return a + b
add(2, 3)
Salida:
calling add
El wrapper captura func en su clausura y le reenvía todo. *args / **kwargs le permite funcionar con cualquier función, no solo con las de dos argumentos. Este patrón es la base de decoradores más elaborados (medición de tiempos, almacenamiento en caché, reintento ante fallos), pero el núcleo es siempre el mismo: recibir una función y devolver una función.