Cosa sono i decorator e come funzionano nel linguaggio Python? In questo articolo lo lo scopriamo assieme!
Python come linguaggio di programmazione, offre una feature abbastanza insolita ma utile: i decorators.
Per fare il precisino e aggiungere un po’ di tecnichese a questo articolo, c’è da specificare che si tratta di metaprogrammazione: in parole semplici è una parte di programma che tenta di modificare un’altra parte del programma in fase di compilazione.
Ciao mi chiamo Lorenzo Neri e sono un informatico: realizzo contenuti per aiutare le persone a padroneggiare l’arte del nuovo millennio, ovvero l’informatica!
Prima di capire nel dettaglio cosa sono e come funzionano i decorator all’interno di Python, è giusto ricordare una cosa.
Qualsiasi cosa in Python è un oggetto.
Sì, qualunque cosa: anche le funzioni.
Le funzioni e i metodi sono “chiamati”… Chiamabili. Sì, italiano portami via.
In inglese diremmo “callable”: in parole dirette e schiette sono “cose che possiamo invocare”.
Se andiamo a vedere nel dettaglio, qualsiasi oggetto che in Python abbia un metodo speciale, noto come “__call__()” è inteso come “callable”.
Muoviamo il nostro primo passo:
Decorator: cosa sono? Sono dei callable che tornano un callable.
Cit. Ma che ca…?!
Cerchiamo di capire un momento la frase di prima.
Decorator: cosa sono? Dei callable che tornano un callable
In termini molto più semplici e comprensibili della frase di prima, un decorator semplicemente è un “qualcosa” che prende una funzione, gli aggiunge delle funzionalità e la ritorna.
Vediamo però un esempio pratico di questo meccanismo:
def aggiungi_funzionalita(funzione):
def funzione_interna():
print("vengo 'decorata'")
funzione()
return funzione_interna
def normale():
print("Io sono una funzione normale")
Vediamo anche l’output di ciò che abbiamo finito di scrivere:
>>> normale()
Io sono una funzione normale
>>> decorata = aggiungi_funzionalita(normale)
>>> decorata()
vengo 'decorata'
Io sono una funzione normale
Nell’esempio che abbiamo appena visto, “aggiungi_funzionalita” è un decorator.
Per farla ancora più “italianeggiante”, la funzione “normale” viene “decorata” e la funzione ritornata all’interno della variabile “decorata”.
Sì, il gioco di parole è voluto per rafforzare il concetto.
Decorator: come funzionano?
Il funzionamento nudo e crudo dei decorator l’abbiamo intravisto poco fa.
Un decorator aggiunge nuove funzionalità alla funzione originale: un po’ come impacchettare un regalo, funziona quasi come un wrapper.
Una precisazione va fatta di fronte a ciò: l’oggetto che ottiene la “decorazione” non viene alterato.
In generale, per stringere il funzionamento in una riga, decoriamo una funzione e la riassegnamo a se stessa:
funzione_ordinaria = decoratore(funzione_ordinaria)
Ma fammi un po’ indovinare? Non è questa la sintassi a cui sei abituato vero?
Infatti in Python si utilizza un altra sintassi.
Si utilizza il simbolo “@” per indicare un decoratore e per “piazzarlo al punto giusto”.
Ti faccio un esempio al volo:
@impacchetta_la_funzione
def funzione_ordinaria():
print("Sono una semplice funzione")
È l’equivalente di:
def funzione_ordinaria():
print("Sono una semplice funzione")
funzione_ordinaria = impacchetta_la_funzione(funzione_ordinaria)
Quindi, fino ad ora dei decorator abbiamo visto sia cosa sono, sia come funzionano.
Ma vediamo un’ultima cosa prima di concludere.
Decorator: come funzionano con i parametri
Se avessimo bisogno di parametrizzare i decorator con i parametri?
Si può fare.
Immagina di avere una funzione parametrica come questa:
def divisione(num1, num2):
return num1 / num2
Sì, lo so: “Lorenzo ma se si divide per zer…” appunto!
Rendiamo questa funzione un po’ più “smart” 🙂
Aggiungiamo un decorator che impedisca la divisione per zero, o la prevenga insomma:
def divisione_intelligente(funzione_base):
def interna(num1, num2):
if num2 == 0:
print("Non puoi dividere per zero!")
return
return funzione_base(num1, num2)
return interna
# usiamo il nostro decorator
@divisione_intelligente
def divisione(num1, num2):
return num1 / num2