Ryuzaki_Eru
18-07-2010, 11:48
Ho scritto questa "guida" sui decoratori in Python dato che molti li considerano ostici e poco chiari e in italiano non c'era praticamente nulla.
Non è di certo il massimo della chiarezza e il mio talento da scrittore evidentemente non esiste o se esiste è molto nascosto :D Ma spero che possa essere utile a qualcuno.
Buona lettura!
--------------------------------------------------------------------------
I decoratori, spesso, mettono in difficoltà o fanno paura anche ai programmatori più navigati. Sarà il loro nome, sarà che a prima vista sembrano arabo, ma fattostà che è cosi.
Ma cerchiamo di capire un pò meglio cosa sono questi fantomatici "decoratori".
Cosa è e cosa fa un decoratore?
Un decoratore è una normale funzione che modifica un'altra funzione. Quando si usa un decoratore, Python passa la funzione da decorare al decoratore, e la sostituisce con il risultato. Facciamo un esempio senza usare la sintassi tipica dei decoratori:
def mio_decoratore(funzione_da_decorare):
#fai qualcosa con la funzione, per es.
funzione_da_decorare.prova = "prova"
return funzione_da_decorare
def molt(a, b):
return a * b
molt = mio_decoratore(molt)
Cosa abbiamo fatto? Per prima cosa abbiamo definito una funzione "molt"(che rappresenta la nostra funzione da decorare) che effettua la moltiplicazione tra due numeri e successivamente l'abbiamo sostituita con la funzione restituita da "mio_decoratore", che altro non è che la funzione "molt" modificata. Infatti, se proviamo a digitare:
molt.prova
otteniamo:
'prova'
Adesso scriviamo un altro codice che fa la stessa identica cosa, ma che usa la sintassi tipica dei decoratori:
def mio_decoratore(funzione_da_decorare):
funzione_da_decorare.prova = "prova"
return funzione_da_decorare
@mio_decoratore
def molt(a, b):
return a * b
La sintassi è molto semplice: basta mettere sopra la funzione da decorare il simbolo @ seguito dal decoratore, in questo caso "mio_decoratore"(può avere un nome qualunque, ovviamente).
Internamente Python applica la funzione decorata al decoratore e la sostituisce con il valore restituito da quest'ultimo. Proprio come nel primo esempio. Semplice no?
Domanda: ma un decoratore deve restituire per forza una funzione? No. Può restituire tutto quello che volete, ma la sua utitlità diventa praticamente nulla :) Immaginate un caso come questo:
def decoratore_inutile(funzione_da_decorare):
return True
@decoratore_inutile
def molt(a, b):
return a * b
Adesso "molt" non è più una funzione, ma un valore booleano, cioè True. Se provate a chiamarla, otterrete il fatidico "TypeError":
>>>molt(5, 5)
>>>TypeError: 'bool' object is not callable
Come vedete non è che serva a molto :)
Un decoratore può restituire una funzione arbitraria, ricordate? Questa funzione la chiameremo "wrapper" e sarà subito chiaro il perchè.
Il giochetto sta nel definire la funzione wrapper all'interno del decoratore, in modo che possa utilizzare le variabili presenti nel suo name space, inclusa quindi anche la funzione da decorare che verrà passata al decoratore.
def mio_decoratore(funzione_da_decorare):
def wrapper():
print "Sono dentro la funzione wrapper e posso accedere " \
"alla funzione %s" % funzione.__name__
return funzione_da_decorare()
return wrapper
@mio_decoratore
def foo():
print "Io sono la funzione da decorare"
>>>foo()
Sono dentro la funzione wrapper e posso accedere alla funzione foo
Io sono la funzione da decorare
Come vedete la funzione wrapper può fare quello che vuole alla funzione da decorare. Ma qualcuno a questo punto si potrà chiedere: e se devo passare degli argomenti alla funzione da decorare?
Semplice: li passiamo alla funzione wrapper. Questo perchè? Perchè quando usiamo il decoratore, la funzione wrapper sostituirà la funzione da decorare. Quindi sarà lei a ricevere gli argomenti in modo da poterli usare per il proprio scopo.
Dal momento che un decoratore può lavorare con qualunque funzione, non sappiamo quanti argomenti avrà la funzione da decorare, quindi il problema si risolve semplicemente facendo accettare alla funzione wrapper argomenti arbitrari non posizionali e a parola chiave, che poi passerà alla funzione da decorare. Ma facciamo un esempio chiarificatore:
def mio_decoratore(funzione_da_decorare):
def wrapper(*args, **kwargs):
print "Stiamo chiamando la funzione %s con argomenti " \
"%s e parole chiave %s" % (funzione_da_decorare.__name__, args, kwargs)
return funzione_da_decorare(*args, **kwargs)
return wrapper
@mio_decoratore
def molt(a, b, foo = "foo"):
print foo
return a * b
>>>molt(1, 2)
Stiamo chiamando la funzione molt con argomenti (1,2) e parole chiave {}
foo
2
Con gli argomenti possiamo fare ciò che vogliamo, ad esempio:
def mio_decoratore(funzione_da_decorare):
def wrapper(*args, **kwargs):
kwargs['foo'] = "argomento modificato"
print "Stiamo chiamando la funzione %s con argomenti " \
"%s e parole chiave %s" % (funzione_da_decorare.__name__, args, kwargs)
return funzione_da_decorare(*args, **kwargs)
return wrapper
@mio_decoratore
def molt(a, b, foo = "foo"):
print foo
return a * b
>>>molt(1, 2)
Stiamo chiamando la funzione molt con argomenti (1,2) e parole chiave {'foo': 'argomento modificato'}
argomento modificato
2
Potresti aver bisogno di personalizzare il comportamento del tuo decoratore passandogli delle opzioni.
Per fare questo dobbiamo definire la nostra funzione decoratore dentro un'altra funzione, che chiameremo "opzioni". Nel momento in cui vogliamo decorare una funzione si usa la solita sintassi(@nome_decoratore), ma invece di usare il nostro decoratore useremo la funzione "opzioni". Vediamo subito un esempio:
def opzioni(valore):
def mio_decoratore(funzione_da_decorare):
funzione_da_decorare.prova = valore
return funzione_da_decorare
return mio_decoratore
@opzioni('test')
def molt(a, b):
return a * b
>>>molt(5, 5)
25
>>>molt.prova
'test'
Non cambia poi molto no? Solo che adesso il nostro decoratore si trova in un ambito dinamico, invece di uno statico :)
E se insieme alle opzioni vogliamo usare anche una funzione wrapper? Il procedimento è esattamente lo stesso:
def opzioni(nome):
def mio_decoratore(funzione_da_decorare):
def wrapper(*args, **kwargs):
kwargs.update({'nome': nome})
print "Chiamo la funzione %s" % funzione_da_decorare
return funzione_da_decorare(*args, **kwargs)
return wrapper
return mio_decoratore
@opzioni("pinco")
def stampa_nome(nome = None):
print "Ciao ", nome
Come avrete già capito, la funzione "opzioni" restituisce il decoratore, che Python userà normalmente; cioè, passerà la funzione da decorare al decoratore e verrà restituito il risultato. Infatti:
>>>stampa_nome
>>><function wrapper at 0x00BBA930>
vedete che "stampa_nome" non è la funzione decoratore(come invece si potrebbe pensare), ma bensì la funzione wrapper. E' quindi evidente che la nostra funzione da decorare("stampa_nome") è stata passata implicitamente per noi da Python ed è stata sostituita con il valore restituito dal decoratore. Bello no? :)
Questo è più o meno tutto.
Esempi d'uso, i decoratori @staticmethod, @classmethod e @property
Riferimenti:
modulo "decorator" scritto da Michele Simionato (http://micheles.googlecode.com/hg/decorator/documentation.html)
--------------------------------------------------------------------------
Il contenuto di questo post è rilasciato con licenza Creative Commons Attribution-Noncommercial-Share Alike 2.5 (http://creativecommons.org/licenses/by-nc-sa/2.5/it/)
Non è di certo il massimo della chiarezza e il mio talento da scrittore evidentemente non esiste o se esiste è molto nascosto :D Ma spero che possa essere utile a qualcuno.
Buona lettura!
--------------------------------------------------------------------------
I decoratori, spesso, mettono in difficoltà o fanno paura anche ai programmatori più navigati. Sarà il loro nome, sarà che a prima vista sembrano arabo, ma fattostà che è cosi.
Ma cerchiamo di capire un pò meglio cosa sono questi fantomatici "decoratori".
Cosa è e cosa fa un decoratore?
Un decoratore è una normale funzione che modifica un'altra funzione. Quando si usa un decoratore, Python passa la funzione da decorare al decoratore, e la sostituisce con il risultato. Facciamo un esempio senza usare la sintassi tipica dei decoratori:
def mio_decoratore(funzione_da_decorare):
#fai qualcosa con la funzione, per es.
funzione_da_decorare.prova = "prova"
return funzione_da_decorare
def molt(a, b):
return a * b
molt = mio_decoratore(molt)
Cosa abbiamo fatto? Per prima cosa abbiamo definito una funzione "molt"(che rappresenta la nostra funzione da decorare) che effettua la moltiplicazione tra due numeri e successivamente l'abbiamo sostituita con la funzione restituita da "mio_decoratore", che altro non è che la funzione "molt" modificata. Infatti, se proviamo a digitare:
molt.prova
otteniamo:
'prova'
Adesso scriviamo un altro codice che fa la stessa identica cosa, ma che usa la sintassi tipica dei decoratori:
def mio_decoratore(funzione_da_decorare):
funzione_da_decorare.prova = "prova"
return funzione_da_decorare
@mio_decoratore
def molt(a, b):
return a * b
La sintassi è molto semplice: basta mettere sopra la funzione da decorare il simbolo @ seguito dal decoratore, in questo caso "mio_decoratore"(può avere un nome qualunque, ovviamente).
Internamente Python applica la funzione decorata al decoratore e la sostituisce con il valore restituito da quest'ultimo. Proprio come nel primo esempio. Semplice no?
Domanda: ma un decoratore deve restituire per forza una funzione? No. Può restituire tutto quello che volete, ma la sua utitlità diventa praticamente nulla :) Immaginate un caso come questo:
def decoratore_inutile(funzione_da_decorare):
return True
@decoratore_inutile
def molt(a, b):
return a * b
Adesso "molt" non è più una funzione, ma un valore booleano, cioè True. Se provate a chiamarla, otterrete il fatidico "TypeError":
>>>molt(5, 5)
>>>TypeError: 'bool' object is not callable
Come vedete non è che serva a molto :)
Un decoratore può restituire una funzione arbitraria, ricordate? Questa funzione la chiameremo "wrapper" e sarà subito chiaro il perchè.
Il giochetto sta nel definire la funzione wrapper all'interno del decoratore, in modo che possa utilizzare le variabili presenti nel suo name space, inclusa quindi anche la funzione da decorare che verrà passata al decoratore.
def mio_decoratore(funzione_da_decorare):
def wrapper():
print "Sono dentro la funzione wrapper e posso accedere " \
"alla funzione %s" % funzione.__name__
return funzione_da_decorare()
return wrapper
@mio_decoratore
def foo():
print "Io sono la funzione da decorare"
>>>foo()
Sono dentro la funzione wrapper e posso accedere alla funzione foo
Io sono la funzione da decorare
Come vedete la funzione wrapper può fare quello che vuole alla funzione da decorare. Ma qualcuno a questo punto si potrà chiedere: e se devo passare degli argomenti alla funzione da decorare?
Semplice: li passiamo alla funzione wrapper. Questo perchè? Perchè quando usiamo il decoratore, la funzione wrapper sostituirà la funzione da decorare. Quindi sarà lei a ricevere gli argomenti in modo da poterli usare per il proprio scopo.
Dal momento che un decoratore può lavorare con qualunque funzione, non sappiamo quanti argomenti avrà la funzione da decorare, quindi il problema si risolve semplicemente facendo accettare alla funzione wrapper argomenti arbitrari non posizionali e a parola chiave, che poi passerà alla funzione da decorare. Ma facciamo un esempio chiarificatore:
def mio_decoratore(funzione_da_decorare):
def wrapper(*args, **kwargs):
print "Stiamo chiamando la funzione %s con argomenti " \
"%s e parole chiave %s" % (funzione_da_decorare.__name__, args, kwargs)
return funzione_da_decorare(*args, **kwargs)
return wrapper
@mio_decoratore
def molt(a, b, foo = "foo"):
print foo
return a * b
>>>molt(1, 2)
Stiamo chiamando la funzione molt con argomenti (1,2) e parole chiave {}
foo
2
Con gli argomenti possiamo fare ciò che vogliamo, ad esempio:
def mio_decoratore(funzione_da_decorare):
def wrapper(*args, **kwargs):
kwargs['foo'] = "argomento modificato"
print "Stiamo chiamando la funzione %s con argomenti " \
"%s e parole chiave %s" % (funzione_da_decorare.__name__, args, kwargs)
return funzione_da_decorare(*args, **kwargs)
return wrapper
@mio_decoratore
def molt(a, b, foo = "foo"):
print foo
return a * b
>>>molt(1, 2)
Stiamo chiamando la funzione molt con argomenti (1,2) e parole chiave {'foo': 'argomento modificato'}
argomento modificato
2
Potresti aver bisogno di personalizzare il comportamento del tuo decoratore passandogli delle opzioni.
Per fare questo dobbiamo definire la nostra funzione decoratore dentro un'altra funzione, che chiameremo "opzioni". Nel momento in cui vogliamo decorare una funzione si usa la solita sintassi(@nome_decoratore), ma invece di usare il nostro decoratore useremo la funzione "opzioni". Vediamo subito un esempio:
def opzioni(valore):
def mio_decoratore(funzione_da_decorare):
funzione_da_decorare.prova = valore
return funzione_da_decorare
return mio_decoratore
@opzioni('test')
def molt(a, b):
return a * b
>>>molt(5, 5)
25
>>>molt.prova
'test'
Non cambia poi molto no? Solo che adesso il nostro decoratore si trova in un ambito dinamico, invece di uno statico :)
E se insieme alle opzioni vogliamo usare anche una funzione wrapper? Il procedimento è esattamente lo stesso:
def opzioni(nome):
def mio_decoratore(funzione_da_decorare):
def wrapper(*args, **kwargs):
kwargs.update({'nome': nome})
print "Chiamo la funzione %s" % funzione_da_decorare
return funzione_da_decorare(*args, **kwargs)
return wrapper
return mio_decoratore
@opzioni("pinco")
def stampa_nome(nome = None):
print "Ciao ", nome
Come avrete già capito, la funzione "opzioni" restituisce il decoratore, che Python userà normalmente; cioè, passerà la funzione da decorare al decoratore e verrà restituito il risultato. Infatti:
>>>stampa_nome
>>><function wrapper at 0x00BBA930>
vedete che "stampa_nome" non è la funzione decoratore(come invece si potrebbe pensare), ma bensì la funzione wrapper. E' quindi evidente che la nostra funzione da decorare("stampa_nome") è stata passata implicitamente per noi da Python ed è stata sostituita con il valore restituito dal decoratore. Bello no? :)
Questo è più o meno tutto.
Esempi d'uso, i decoratori @staticmethod, @classmethod e @property
Riferimenti:
modulo "decorator" scritto da Michele Simionato (http://micheles.googlecode.com/hg/decorator/documentation.html)
--------------------------------------------------------------------------
Il contenuto di questo post è rilasciato con licenza Creative Commons Attribution-Noncommercial-Share Alike 2.5 (http://creativecommons.org/licenses/by-nc-sa/2.5/it/)