tl; dr
È possibile risolvere questo problema mediante la conversione della classe Timed
un descrittore e il ritorno di una funzione parzialmente applicata da __get__
quale si applica l'oggetto Test
come uno degli argomenti, come questo
class Timed(object):
def __init__(self, f):
self.func = f
def __call__(self, *args, **kwargs):
print self
start = dt.datetime.now()
ret = self.func(*args, **kwargs)
time = dt.datetime.now() - start
ret["time"] = time
return ret
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
Il vero problema
Citando la documentazione Python per decorator,
La sintassi decoratore è lo zucchero meramente sintattico, le due seguenti definizioni di funzione sono semanticamente equivalenti:
def f(...):
...
f = staticmethod(f)
@staticmethod
def f(...):
...
Così, quando tu dici,
@Timed
def decorated(self, *args, **kwargs):
realtà è
decorated = Timed(decorated)
solo l'oggetto funzione è passato al Timed
, l'oggetto a cui è vincolata in realtà non si trasmette con esso.Così, quando si richiama in questo modo
ret = self.func(*args, **kwargs)
self.func
farà riferimento alla oggetto funzione non legato e viene richiamata con Hello
come primo argomento. Questo è il motivo per cui self
viene stampato come Hello
.
Come posso risolvere questo?
Dal momento che non si ha riferimento all'istanza Test
nel Timed
, l'unico modo per farlo sarebbe quello di convertire Timed
come classe descrittore. Citando la documentazione, Invoking descriptors sezione
In generale, un descrittore è un attributo oggetto con “binding comportamento”, una cui accesso attributo sia stata ripristinata con i metodi del protocollo descrittore: __get__()
, __set__()
e __delete__()
. Se uno di questi metodi è definito per un oggetto, si dice che sia un descrittore.
Il comportamento predefinito per l'accesso agli attributi è ottenere, impostare o eliminare l'attributo dal dizionario di un oggetto. Ad esempio, a.x
ha una catena di ricerca che inizia con a.__dict__['x']
, quindi type(a).__dict__['x']
e continua attraverso le classi base di type(a)
esclusi i metaclassi.
Tuttavia, se il valore cercato è un oggetto che definisce uno dei metodi descrittori, Python può sovrascrivere il comportamento predefinito e richiamare il metodo descrittore invece.
Possiamo fare Timed
un descrittore, definendo semplicemente un metodo come questo
def __get__(self, instance, owner):
...
Qui, self
riferisce all'oggetto Timed
stessa, instance
riferisce all'oggetto effettiva in cui la ricerca attributo avviene e owner
fa riferimento alla classe corrispondente allo instance
.
Ora, quando __call__
viene richiamato su Timed
, verrà invocato il metodo __get__
. Ora, in qualche modo, è necessario passare il primo argomento come istanza della classe Test
(anche prima dello Hello
). Quindi, creiamo un'altra funzione parzialmente applicata, il cui primo parametro sarà l'istanza Test
, simili
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
Ora, self.__call__
è un metodo vincolato (vincolati a Timed
esempio) e il secondo parametro partial
è il primo argomento alla chiamata self.__call__
.
Quindi, tutti questi si traducono in modo efficace come questo
t.call_deco()
self.decorated("Hello", world="World")
Ora self.decorated
è in realtà Timed(decorated)
(questo sarà indicato come TimedObject
d'ora in poi) oggetto.Ogni volta che accediamo, verrà invocato il metodo __get__
definito e verrà restituita una funzione partial
. È possibile confermare che in questo modo
def call_deco(self):
print self.decorated
self.decorated("Hello", world="World")
sarebbe stampare
<functools.partial object at 0x7fecbc59ad60>
...
Quindi,
self.decorated("Hello", world="World")
si traduce per
Timed.__get__(TimedObject, <Test obj>, Test.__class__)("Hello", world="World")
Essendo restituire una funzione di partial
,
partial(TimedObject.__call__, <Test obj>)("Hello", world="World"))
che è in realtà
TimedObject.__call__(<Test obj>, 'Hello', world="World")
Quindi, <Test obj>
diventa anche una parte di *args
, e quando self.func
viene invocato, il primo argomento sarà la <Test obj>
.
Un rapido google mostra questo: http://thecodeship.com/patterns/guide-to-python-function-decorators/ (vedere la sezione "Metodi di decorazione") – cfh
Hai letto per es. http://stackoverflow.com/q/2365701/3001761, http://stackoverflow.com/q/15098424/3001761 – jonrsharpe
Non ti imbatterai in questo tipo di problema quando utilizzi una funzione come decoratore anziché come callable oggetto. – poke