Decoratori in Python
I decoratori in Python sono una potente funzionalità che consente di modificare o estendere il comportamento di funzioni o classi senza modificarne direttamente il codice sorgente. I decoratori sono ampiamente utilizzati in molte librerie Python e offrono una sintassi semplice per applicare modifiche a funzioni, classi o metodi.
Cosa sono i Decoratori?
Un decoratore è una funzione che accetta un’altra funzione come argomento e restituisce una nuova funzione, tipicamente modificata o estesa. Questo consente di aggiungere funzionalità a una funzione esistente in modo flessibile.
Sintassi di un Decoratore
Il decoratore si applica utilizzando la @
seguita dal nome del decoratore sopra la definizione della funzione che si desidera decorare. Ecco un esempio semplice:
def decoratore_di_esempio(funzione):
def wrapper():
print("Prima della funzione")
funzione()
print("Dopo la funzione")
return wrapper
@decoratore_di_esempio
def funzione_esempio():
print("Funzione principale")
funzione_esempio()
Output:
Prima della funzione
Funzione principale
Dopo la funzione
Nel codice sopra:
decoratore_di_esempio
è un decoratore che avvolge la funzionefunzione_esempio
.- La funzione originale è incapsulata all’interno di
wrapper()
, che aggiunge un comportamento prima e dopo l’esecuzione della funzione principale.
Creazione di un Decoratore
Vediamo come creare un decoratore passo dopo passo. Un decoratore classico accetta una funzione come argomento, definisce un wrapper
interno che racchiude il comportamento aggiuntivo e restituisce il wrapper
.
Esempio: Decoratore che misura il tempo di esecuzione
Un esempio pratico di decoratore è uno che misura quanto tempo impiega una funzione per essere eseguita.
import time
def calcola_tempo(funzione):
def wrapper(*args, **kwargs):
inizio = time.time()
risultato = funzione(*args, **kwargs)
fine = time.time()
print(f"Tempo di esecuzione: {fine - inizio:.4f} secondi")
return risultato
return wrapper
@calcola_tempo
def somma_numeri(a, b):
time.sleep(1) # Simula un'operazione lunga 1 secondo
return a + b
print(somma_numeri(5, 10))
Output:
Tempo di esecuzione: 1.0001 secondi
15
In questo esempio:
- Il decoratore
calcola_tempo
misura quanto tempo ci vuole per eseguire la funzionesomma_numeri
. - Grazie a
@calcola_tempo
, la funzionesomma_numeri
viene automaticamente “decorata” con il codice per il calcolo del tempo.
Passare Argomenti ai Decoratori
I decoratori possono lavorare con funzioni che accettano argomenti utilizzando *args
e **kwargs
. Questo consente al decoratore di essere flessibile e applicabile a qualsiasi funzione, indipendentemente dal numero di parametri.
Ecco un esempio di un decoratore che logga gli argomenti passati a una funzione:
def log_args(funzione):
def wrapper(*args, **kwargs):
print(f"Chiamata a {funzione.__name__} con argomenti: {args}, {kwargs}")
return funzione(*args, **kwargs)
return wrapper
@log_args
def moltiplica(a, b):
return a * b
print(moltiplica(3, 4))
Output:
Chiamata a moltiplica con argomenti: (3, 4), {}
12
Decoratori Multipli
È possibile applicare più decoratori a una singola funzione. La sintassi prevede la sovrapposizione dei decoratori uno sopra l’altro.
Ecco un esempio:
def decoratore_a(funzione):
def wrapper(*args, **kwargs):
print("Decoratore A inizia")
risultato = funzione(*args, **kwargs)
print("Decoratore A finisce")
return risultato
return wrapper
def decoratore_b(funzione):
def wrapper(*args, **kwargs):
print("Decoratore B inizia")
risultato = funzione(*args, **kwargs)
print("Decoratore B finisce")
return risultato
return wrapper
@decoratore_a
@decoratore_b
def funzione_prova():
print("Funzione principale")
funzione_prova()
Output:
Decoratore A inizia
Decoratore B inizia
Funzione principale
Decoratore B finisce
Decoratore A finisce
In questo caso:
- I decoratori vengono applicati dall’alto verso il basso. Quindi
decoratore_a
avvolgedecoratore_b
, che a sua volta avvolge la funzionefunzione_prova
.
Decoratori per Classi
I decoratori non si applicano solo alle funzioni, ma possono anche decorare le classi. Un decoratore di classe può modificare o estendere il comportamento di un’intera classe.
Ecco un esempio di decoratore che aggiunge un metodo a una classe:
def aggiungi_metodo(cls):
cls.saluta = lambda self: print(f"Ciao, sono {self.nome}!")
return cls
@aggiungi_metodo
class Persona:
def __init__(self, nome):
self.nome = nome
p = Persona("Marco")
p.saluta() # Output: Ciao, sono Marco!
In questo esempio:
- Il decoratore
aggiungi_metodo
aggiunge dinamicamente un nuovo metodosaluta
alla classePersona
.
Decoratori con Parametri
In alcuni casi, potresti voler passare dei parametri a un decoratore. In questo caso, è necessario creare un decoratore annidato, che genera un decoratore effettivo.
Ecco un esempio:
def ripeti(n):
def decoratore(funzione):
def wrapper(*args, **kwargs):
for _ in range(n):
funzione(*args, **kwargs)
return wrapper
return decoratore
@ripeti(3)
def saluta():
print("Ciao!")
saluta()
Output:
Ciao!
Ciao!
Ciao!
Il decoratore ripeti
prende un argomento n
che specifica quante volte eseguire la funzione decorata.
Vantaggi dell’Utilizzo dei Decoratori
- Riutilizzo del codice: I decoratori permettono di incapsulare funzionalità comuni (ad esempio logging, controllo degli accessi, caching) e applicarle a diverse funzioni o classi senza duplicare il codice.
- Modularità: Separano la logica accessoria (come la misurazione del tempo o il logging) dal comportamento principale della funzione.
- Leggibilità: I decoratori possono rendere il codice più leggibile, poiché la loro applicazione è immediatamente evidente dalla sintassi.
Considerazioni Finali
I decoratori sono uno strumento potente che consente di estendere o modificare il comportamento delle funzioni e delle classi in modo modulare e flessibile. Con i decoratori, è possibile separare la logica principale del programma da quella accessoria, mantenendo il codice più pulito e riutilizzabile.