Lezioni del modulo (4/4)
Decoratori: funzioni che modificano funzioni
Un decoratore è una funzione che riceve un'altra funzione e ne ritorna una versione "decorata". È un modo potente per aggiungere comportamento (logging, timing, cache, retry, validazione) senza toccare il corpo della funzione originale.
Funzioni come valori
In Python le funzioni sono oggetti: le puoi passare, ritornare, assegnare.
def saluta():
print("ciao")
f = saluta # NIENTE parentesi: assegno la funzione
f() # 'ciao'Un decoratore minimale
def log(func):
def wrapper(*args, **kwargs):
print(f"chiamo {func.__name__}")
risultato = func(*args, **kwargs)
print(f"ho finito {func.__name__}")
return risultato
return wrapper
def somma(a, b):
return a + b
somma = log(somma) # ora somma è "wrapper"
somma(2, 3)
# chiamo somma
# ho finito somma
# 5Sintassi @decorator
L'assegnazione somma = log(somma) si scrive più elegantemente con @:
@log
def somma(a, b):
return a + bÈ esattamente la stessa cosa. @log sopra la definizione equivale a:
"appena definita questa funzione, sostituiscila con log(funzione)".
*args, **kwargs: accetta qualsiasi firma
def wrapper(*args, **kwargs):
return func(*args, **kwargs)*args raccoglie i posizionali, **kwargs i nominati. Servono per scrivere
wrapper che funzionano con qualunque funzione.
functools.wraps: preserva i metadati
Senza wraps, la funzione decorata perde __name__, __doc__, hint:
from functools import wraps
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
...
return func(*args, **kwargs)
return wrapperEsempio: @timeit
from functools import wraps
import time
def timeit(func):
@wraps(func)
def wrapper(*args, **kwargs):
t0 = time.perf_counter()
risultato = func(*args, **kwargs)
dt = time.perf_counter() - t0
print(f"{func.__name__} ha impiegato {dt*1000:.2f} ms")
return risultato
return wrapper
@timeit
def somma_grande():
return sum(range(10**6))
somma_grande()Decoratori dalla stdlib
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2: return n
return fib(n - 1) + fib(n - 2)
fib(100) # istantaneo grazie alla cache@lru_cache— memorizza i risultati di una funzione pura.@property— espone un metodo come attributo (visto nel modulo OOP).@staticmethod,@classmethod— modificano come viene passatoself.
Decoratori con argomenti
I decoratori possono anche accettare argomenti. Per farlo, occorre definire tre livelli di funzioni annidate: il livello esterno riceve gli argomenti del decoratore, il secondo livello riceve la funzione da decorare, ed il terzo livello (wrapper) riceve i parametri reali passati alla funzione decorata durante la chiamata.
Prova tu
Scrivi un decoratore `double_result` che chiama la funzione decorata e ritorna il suo risultato * 2. Applicalo a una funzione `f(x)` che ritorna x + 1. Valuta `f(10)`.
Mostra suggerimento
Il wrapper chiama func e ritorna il risultato moltiplicato per 2.
Soluzione disponibile dopo 3 tentativi
Esercizio di ripasso
Usa functools.lru_cache per accelerare una funzione fib(n) ricorsiva. Calcola fib(30) e assegna a `r`. Valuta `r`.
Mostra suggerimento
@lru_cache(maxsize=None) sopra la def.
Soluzione disponibile dopo 3 tentativi
Sfida aggiuntiva
Definisci un decoratore `double_result` che raddoppi (moltiplicando per 2) il valore restituito dalla funzione decorata. Applica il decoratore alla funzione `def sum_nums(a, b): return a + b`. Valuta infine `sum_nums(3, 4)`.
Mostra suggerimento
Il wrapper chiama func(*args, **kwargs) e moltiplica il risultato per 2.
Soluzione disponibile dopo 3 tentativi