2009-08-11 13 views
89

¿Se puede escribir algo como:decoradores de Python en clases

class Test(object): 
    def _decorator(self, foo): 
     foo() 

    @self._decorator 
    def bar(self): 
     pass 

Esta falla: auto en @self se desconoce

También probé:

@Test._decorator(self) 

que también falla: Prueba desconocida

Si le gustaría tener la temperatura. cambie algunas variables de instancia en el decorador y ejecute el método decorado, antes de cambiándolos de nuevo.

Gracias.

Respuesta

52

Lo que quieres hacer no es posible. Tomemos, por ejemplo, si el código de abajo parece válida:

class Test(object): 

    def _decorator(self, foo): 
     foo() 

    def bar(self): 
     pass 
    bar = self._decorator(bar) 

Es, por supuesto, no es válido, ya que self no está definido en ese punto. Lo mismo vale para Test ya que no se definirá hasta que se defina la clase (que está en proceso de). Te estoy mostrando este fragmento de código porque esto es en lo que se transforma tu fragmento de decorador.

Así, como puede ver, acceder a la instancia en un decorador como ese no es posible ya que los decoradores se aplican durante la definición de cualquier función/método al que estén conectados y no durante la creación de instancias.

Si necesita acceso a nivel de clase, intente esto:

class Test(object): 

    @classmethod 
    def _decorator(cls, foo): 
     foo() 

    def bar(self): 
     pass 
Test.bar = Test._decorator(Test.bar) 
+6

"imposible" es objetivamente incorrecto. – zneak

+3

probablemente debería actualizarse para hacer referencia a la respuesta más precisa debajo de – natb1

168

¿Podría hacer algo como esto lo que necesita?

class Test(object): 
    def _decorator(foo): 
     def magic(self) : 
      print "start magic" 
      foo(self) 
      print "end magic" 
     return magic 

    @_decorator 
    def bar(self) : 
     print "normal call" 

test = Test() 

test.bar() 

Esto evita la llamada a uno mismo para acceder al decorador y lo deja oculto en el espacio de nombres de clases como un método regular.

>>> import stackoverflow 
>>> test = stackoverflow.Test() 
>>> test.bar() 
start magic 
normal call 
end magic 
>>> 

editado a responder a la pregunta en los comentarios:

Cómo utilizar el decorador oculto en otra clase

class Test(object): 
    def _decorator(foo): 
     def magic(self) : 
      print "start magic" 
      foo(self) 
      print "end magic" 
     return magic 

    @_decorator 
    def bar(self) : 
     print "normal call" 

    _decorator = staticmethod(_decorator) 

class TestB(Test): 
    @Test._decorator 
    def bar(self): 
     print "override bar in" 
     super(TestB, self).bar() 
     print "override bar out" 

print "Normal:" 
test = Test() 
test.bar() 
print 

print "Inherited:" 
b = TestB() 
b.bar() 
print 
+0

Gracias por su respuesta. Sí, esto funcionaría si no fuera por el hecho de que quería que el decorador realizara algunas operaciones en las variables de instancia, y eso requeriría un autocontrol. – hcvst

+4

¿El decorador o la función decorada? Tenga en cuenta que la función "mágica" devuelta que envuelve la barra recibe una autovariable arriba cuando se invoca "barra" en una instancia y puede hacer cualquier cosa con las variables de instancia que quería antes y después (o incluso si) llamaba "barra" . No existen variables de instancia al declarar la clase. ¿Querías hacer algo para la clase desde dentro del decorador? No creo que sea un uso idiomático. –

+0

Gracias Michael, solo que ahora vi que esto es lo que quería. – hcvst

2

me encontré con esta pregunta mientras investigaba un problema muy similar. Mi solución es dividir el problema en dos partes. En primer lugar, debe capturar los datos que desea asociar con los métodos de clase. En este caso, handler_for asociará un comando Unix con controlador para la salida de ese comando.

class OutputAnalysis(object): 
    "analyze the output of diagnostic commands" 
    def handler_for(name): 
     "decorator to associate a function with a command" 
     def wrapper(func): 
      func.handler_for = name 
      return func 
     return wrapper 
    # associate mount_p with 'mount_-p.txt' 
    @handler_for('mount -p') 
    def mount_p(self, slurped): 
     pass 

Ahora que hemos asociado algunos datos con cada método de clase, tenemos que recopilar esos datos y almacenarlos en un atributo de clase.

OutputAnalysis.cmd_handler = {} 
for value in OutputAnalysis.__dict__.itervalues(): 
    try: 
     OutputAnalysis.cmd_handler[value.handler_for] = value 
    except AttributeError: 
     pass 
4

utilizo este tipo de decorador en algunas situaciones de depuración, que permite a las propiedades de clase primordiales de la decoración, sin tener que encontrar la función de llamada.

class myclass(object): 
    def __init__(self): 
     self.property = "HELLO" 

    @adecorator(property="GOODBYE") 
    def method(self): 
     print self.property 

Este es el código decorador

class adecorator (object): 
    def __init__ (self, *args, **kwargs): 
     # store arguments passed to the decorator 
     self.args = args 
     self.kwargs = kwargs 

    def __call__(self, func): 
     def newf(*args, **kwargs): 

      #the 'self' for a method function is passed as args[0] 
      slf = args[0] 

      # replace and store the attributes 
      saved = {} 
      for k,v in self.kwargs.items(): 
       if hasattr(slf, k): 
        saved[k] = getattr(slf,k) 
        setattr(slf, k, v) 

      # call the method 
      ret = func(*args, **kwargs) 

      #put things back 
      for k,v in saved.items(): 
       setattr(slf, k, v) 

      return ret 
     newf.__doc__ = func.__doc__ 
     return newf 

Nota: porque yo he utilizado un decorador de clase que necesita para utilizar @adecorator() con los soportes en para decorar funciones, incluso si no pasa ningún argumento al constructor de la clase decorador.

1

decoradores encajar mejor para modificar la funcionalidad de un entero objeto (incluyendo objetos de función) frente a la funcionalidad de un método objeto que en general dependerá de los atributos de instancia. Por ejemplo:

def mod_bar(cls): 
    # returns modified class 

    def decorate(fcn): 
     # returns decorated function 

     def new_fcn(self): 
      print self.start_str 
      print fcn(self) 
      print self.end_str 

     return new_fcn 

    cls.bar = decorate(cls.bar) 
    return cls 

@mod_bar 
class Test(object): 
    def __init__(self): 
     self.start_str = "starting dec" 
     self.end_str = "ending dec" 

    def bar(self): 
     return "bar" 

la salida es:

>>> import Test 
>>> a = Test() 
>>> a.bar() 
starting dec 
bar 
ending dec 
0

Se puede decorar el decorador:

import decorator 

class Test(object): 
    @decorator.decorator 
    def _decorator(foo, self): 
     foo(self) 

    @_decorator 
    def bar(self): 
     pass 
2

Ésta es una forma que conozco (y utilizado) para acceder a self de dentro de un decorador definido dentro de la misma clase:

class Thing(object): 
    def __init__(self, name): 
     self.name = name 

    def debug_name(function): 
     def debug_wrapper(*args): 
      self = args[0] 
      print 'self.name = ' + self.name 
      print 'running function {}()'.format(function.__name__) 
      function(*args) 
      print 'self.name = ' + self.name 
     return debug_wrapper 

    @debug_name 
    def set_name(self, new_name): 
     self.name = new_name 

de salida (probado en pitón 2.7.10):

>>> a = Thing('A') 
>>> a.name 
'A' 
>>> a.set_name('B') 
self.name = A 
running function set_name() 
self.name = B 
>>> a.name 
'B' 

El ejemplo anterior es tonto, pero demuestra que funciona.

3
class Example(object): 

    def wrapper(func): 
     @functools.wraps(func) 
     def wrap(self, *args, **kwargs): 
      print "inside wrap" 
      return func(self, *args, **kwargs) 
     return wrap 

    @wrapper 
    def method(self): 
     pass 

    wrapper = staticmethod(wrapper) 
1

Aquí hay una expansión en la respuesta de Michael Speer tomarlo un poco más allá:

Un decorador método de instancia que tiene argumentos y las pone en una función con argumentos y un valor de retorno.

class Test(object): 
    "Prints if x == y. Throws an error otherwise." 
    def __init__(self, x): 
     self.x = x 

    def _outer_decorator(y): 
     def _decorator(foo): 
      def magic(self, *args, **kwargs) : 
       print("start magic") 
       if self.x == y: 
        return foo(self, *args, **kwargs) 
       else: 
        raise ValueError("x ({}) != y ({})".format(self.x, y)) 
       print("end magic") 
      return magic 

     return _decorator 

    @_outer_decorator(y=3) 
    def bar(self, *args, **kwargs) : 
     print("normal call") 
     print("args: {}".format(args)) 
     print("kwargs: {}".format(kwargs)) 

     return 27 

Y luego

In [2]: 

    test = Test(3) 
    test.bar(
     13, 
     'Test', 
     q=9, 
     lollipop=[1,2,3] 
    ) 
    ​ 
    start magic 
    normal call 
    args: (13, 'Test') 
    kwargs: {'q': 9, 'lollipop': [1, 2, 3]} 
Out[2]: 
    27 
In [3]: 

    test = Test(4) 
    test.bar(
     13, 
     'Test', 
     q=9, 
     lollipop=[1,2,3] 
    ) 
    ​ 
    start magic 
    --------------------------------------------------------------------------- 
    ValueError        Traceback (most recent call last) 
    <ipython-input-3-576146b3d37e> in <module>() 
      4  'Test', 
      5  q=9, 
    ----> 6  lollipop=[1,2,3] 
      7) 

    <ipython-input-1-428f22ac6c9b> in magic(self, *args, **kwargs) 
     11      return foo(self, *args, **kwargs) 
     12     else: 
    ---> 13      raise ValueError("x ({}) != y ({})".format(self.x, y)) 
     14     print("end magic") 
     15    return magic 

    ValueError: x (4) != y (3)