Come posso usare una variabile di istanza di classe come argomento per un decoratore di metodi in Python?
Domanda
Come posso usare una variabile di istanza di classe come argomento per un decoratore di metodi in Python? Il seguente esempio minimo mostra cosa sto cercando di fare. Ovviamente fallisce perché la funzione decoratore non ha accesso al riferimento all'istanza e non ho idea di come accedere al riferimento dal decoratore.
def decorator1(arg1):
def wrapper(function):
print "decorator argument: %s" % arg1
return function
return wrapper
class Foo(object):
def __init__(self, arg1):
self.var1 = arg1
@decorator1(self.var1)
def method1(self):
print "method1"
foo = Foo("abc")
foo.method1()
Soluzione
Non funzionerà; il decoratore viene chiamato durante la creazione della classe , che è molto prima che venga creata un'istanza ( se che accada mai). Quindi se il tuo "decoratore" ha bisogno dell'istanza, devi fare la "decorazione" al momento dell'istanza:
def get_decorator(arg1):
def my_decorator(function):
print "get_decorator argument: %s" % arg1
return function
return my_decorator
class Foo(object):
def __init__(self, arg1):
self.var1 = arg1
self.method1 = get_decorator(self.var1)(self.method1)
def method1(self):
print "method1"
foo = Foo("abc")
foo.method1()
Nota che ho cambiato i nomi delle funzioni in base al loro significato; l'attuale "decoratore", ovvero la funzione che (potenzialmente) modifica il metodo, è wrapper
nel tuo caso, non decorator1
.
Altri suggerimenti
Il tuo & # 8220; ordito & # 8221; la funzione è in realtà un decoratore, piuttosto che un ordito. Il tuo & # 8220; decoratore1 & # 8221; la funzione è un costruttore di decoratori. Se vuoi avere accesso a self.var1 in runtime devi creare un warper non un decoratore:
def decorator(function):
def wrapper(self,*args,**kwargs):
print "Doing something with self.var1==%s" % self.var1
return function(self,*args,**kwargs)
return wrapper
class Foo(object):
def __init__(self, arg1):
self.var1 = arg1
@decorator
def method1(self):
print "method1"
foo = Foo("abc")
foo.method1()
Se vuoi avere un decoratore più generico, è meglio dichiarare una classe richiamabile:
class decorator:
def __init__(self,varname):
self.varname = varname
def __call__(self,function):
varname=self.varname
def wrapper(self,*args,**kwargs):
print "Doing something with self.%s==%s" % (varname,getattr(self,varname))
return function(self,*args,**kwargs)
return wrapper
Utilizzo:
@decorator("var1")
Il decoratore viene eseguito quando viene definita la classe, quindi non è possibile passargli una variabile di istanza.
Ecco come lo facevamo in passato.
class Foo(object):
def __init__(self, arg1):
self.var1 = arg1
def method1(self):
self.lock()
try:
self.do_method1()
except Exception:
pass # Might want to log this
finally:
self.unlock()
def do_method1(self):
print "method1"
def lock(self):
print "locking: %s" % self.arg1
def unlock(self):
print "unlocking: %s" % self.arg1
Ora, una sottoclasse deve solo sovrascrivere do_method1
per ottenere i vantaggi del "wrapping". Fatto alla vecchia maniera, senza con
.
Sì, è prolisso. Tuttavia, non comporta nessuna magia.