Decoradores Python en clases


Se puede escribir sth como:

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

    @self._decorator
    def bar(self):
        pass

Esto falla: self en @self es desconocido

También intenté:

@Test._decorator(self)

Que también falla: Prueba desconocida

Si desea temp. cambiar algunas variables de instancia en el decorador y la ejecución del método decorado, antes cambiarlos de nuevo.

Gracias.

Author: User97693321, 2009-08-12

9 answers

Lo que quieres hacer no es posible. Tomemos, por ejemplo, si el siguiente código parece o no válido:

class Test(object):

    def _decorator(self, foo):
        foo()

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

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

Así que, como puedes ver, accediendo a la instancia en un decorador como ese no es realmente posible, ya que los decoradores se aplican durante la definición de cualquier función/método al que se adjuntan y no durante la instanciación.

Si necesitas acceso a nivel de clase, prueba esto:

class Test(object):

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

    def bar(self):
        pass
Test.bar = Test._decorator(Test.bar)
 52
Author: Evan Fosmark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-11 23:39:29

¿Haría algo como esto lo que necesitas?

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 self para acceder al decorador y lo deja oculto en el espacio de nombres de la clase como un método regular.

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

Editado para responder a la pregunta en los comentarios:

Cómo usar 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
 191
Author: Michael Speer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-15 01:15:17
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)
 12
Author: madjardi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-17 12:48:55

Utilizo este tipo de decorador en algunas situaciones de depuración, permite sobreescribir las propiedades de la clase decorando, sin tener que encontrar la función que llama.

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

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

Aquí está 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: debido a que he usado un decorador de clases, necesitarás usar @adecorator() con los corchetes para decorar funciones, incluso si no pasas ningún argumento al constructor de clases del decorador.

 5
Author: digitalacorn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-20 09:13:09

Esta es una forma que conozco (y he usado) para acceder self desde 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

Salida (probada en python 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 muestra que funciona.

 5
Author: Gunnar Hermansson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-07 09:43:25

Encontré esta pregunta mientras investigaba un problema muy similar. Mi solución es dividir el problema en dos partes. Primero, necesita capturar los datos que desea asociar con los métodos de clase. En este caso, handler_for asociará un comando Unix con handler 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, necesitamos 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
 3
Author: samwyse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-17 17:48:25

Aquí hay una expansión de la respuesta de Michael Speer para llevarlo unos pasos más allá:

Un decorador de métodos de instancia que toma argumentos y actúa sobre una función con argumentos y un valor devuelto.

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)
 3
Author: Oliver Evans,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-10 00:10:38

Los decoradores parecen más adecuados para modificar la funcionalidad de un objeto completo (incluidos los objetos de función) en comparación con la funcionalidad de un método de objeto que en general dependerá de los atributos de la 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
 2
Author: nicodjimenez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-27 02:22:25

Puedes decorar el decorador:

import decorator

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

    @_decorator
    def bar(self):
        pass
 1
Author: doep,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-13 15:37:39