¿Cómo puedo usar una variable de instancia de clase como argumento para un decorador de métodos en Python?
Pregunta
¿Cómo puedo usar una variable de instancia de clase como argumento para un decorador de métodos en Python? El siguiente es un ejemplo mínimo que muestra lo que estoy tratando de hacer. Obviamente falla ya que la función de decorador no tiene acceso a la referencia a la instancia y no tengo idea de cómo obtener acceso a la referencia desde el decorador.
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()
Solución
No va a funcionar; se llama al decorador durante el tiempo de creación de clase , que es mucho antes de que se cree una instancia ( if que ocurra alguna vez). Entonces, si tu " decorador " necesita la instancia, tienes que hacer la "decoración" en el momento de la instanciación:
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()
Tenga en cuenta que cambié los nombres de las funciones de acuerdo con sus significados; el "decorador" real, es decir, la función que (potencialmente) modifica el método, es wrapper
en su caso, no decorator1
.
Otros consejos
Su & # 8220; warper & # 8221; La función es en realidad un decorador, en lugar de un warper. Su & ??# 8220; decorador1 & # 8221; La función es un constructor decorador. Si desea tener acceso a self.var1 en tiempo de ejecución, debe hacer un warper no decorador:
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()
Si desea tener un decorador más genérico, es mejor declarar una clase invocable:
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
Uso:
@decorator("var1")
El decorador se ejecuta cuando se define la clase, por lo que no puede pasarle una variable de instancia.
Así es como solíamos hacer esto en los viejos tiempos.
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
Ahora, una subclase solo necesita anular do_method1
para obtener los beneficios del " wrapping " ;. Hecho a la antigua usanza, sin ninguna instrucción with
.
Sí, es de largo aliento. Sin embargo, tampoco implica ninguna magia.