2.23. Decoratori

Un decoratore è un costrutto sintattico che avvolge una funzione (o un metodo) in un’altra funzione. Il wrapper può aggiungere comportamento prima e dopo la chiamata originale, sostituire il valore di ritorno o associare metadati. La forma è:

@wrapper
def f():
    ...

La riga @wrapper equivale a f = wrapper(f) – la funzione f viene costruita normalmente, poi passata a wrapper, e il risultato viene riassegnato al nome f.

Una funzione che entra in un riquadro «wrapper» a sinistra ed esce a destra come funzione decorata.

Un decoratore prende in ingresso una funzione e restituisce una nuova funzione.

2.23.1. Decoratori di metodo integrati

Alcuni decoratori sono forniti con Python e si usano all’interno dei corpi delle classi.

2.23.1.1. @property

Trasforma un metodo in un attributo calcolato. Il chiamante vi accede come se fosse un normale attributo (senza parentesi), ma a ogni lettura viene eseguito un metodo:

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

Usalo quando un attributo che sembra semplice necessita di un piccolo calcolo dietro le quinte. Se il lavoro è oneroso, preferisci un metodo normale – i chiamanti non si aspettano che le letture di attributo siano lente.

2.23.1.2. @classmethod

Definisce un metodo che riceve la classe come primo argomento anziché un’istanza. Il primo parametro è convenzionalmente chiamato 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()

I metodi di classe sono il modo standard per fornire costruttori alternativi – funzioni factory che restituiscono un’istanza costruita in modo non predefinito.

2.23.1.3. @staticmethod

Definisce un metodo che non riceve né l’istanza né la classe – è semplicemente una funzione normale che vive nel namespace della classe per motivi organizzativi:

class Temperature:
    @staticmethod
    def c_to_f(c):
        return c * 9 / 5 + 32

Temperature.c_to_f(100)   # 212.0

Usalo con parsimonia; se una funzione non ha davvero nulla a che fare con lo stato della classe, una semplice funzione a livello di modulo è di solito più pulita.

2.23.2. Scrivere un decoratore personalizzato

Un decoratore è una funzione che prende una funzione e restituisce una funzione. La forma minima:

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)

Output:

calling add

Il wrapper cattura func come closure e gli inoltra tutto. *args / **kwargs gli permette di funzionare su qualsiasi funzione, non solo su quelle a due argomenti. Questo pattern è il fondamento di decoratori più elaborati (timing, caching, retry-on-failure) ma il nucleo è sempre lo stesso: prendi in ingresso una funzione, restituisci in uscita una funzione.