Decoradores Python en clases

140

¿Se puede escribir algo como:

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

    @self._decorator
    def bar(self):
        pass

Esto falla: self in @self es desconocido

También probé:

@Test._decorator(self)

que también falla: prueba desconocida

Me gustaría cambiar temporalmente algunas variables de instancia en el decorador y luego ejecutar el método decorado, antes de volver a cambiarlas.

hcvst
fuente

Respuestas:

268

¿Algo como esto haría 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 sí mismo 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 preguntas 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

Salida:

Normal:
start magic
normal call
end magic

Inherited:
start magic
override bar in
start magic
normal call
end magic
override bar out
end magic
Michael Speer
fuente
77
¿El decorador o la función decorada? Tenga en cuenta que la función "mágica" devuelta que envuelve la barra está recibiendo una variable propia arriba cuando se llama a "barra" en una instancia y podría hacer cualquier cosa a las variables de instancia que quería antes y después (o incluso si llamó o no "barra" . No existen variables de instancia al declarar la clase. ¿Querías hacerle algo a la clase desde el decorador? No creo que sea un uso idiomático.
Michael Speer
1
Gracias Michael, solo ahora vi que esto es lo que quería.
hcvst
2
Encuentro esta solución mucho mejor que la respuesta aceptada, ya que permite el uso de la sintaxis @ decorator en el punto de definición. Si tengo que poner llamadas de decorador al final de la clase, entonces no está claro que las funciones se estén decorando. Es un poco extraño que no puedas usar @staticmethod en el decorador, pero al menos funciona.
mgiuca
1
No creo que funcione si creo una clase heredada de Test. Por ejemplo: class TestB (Test): @_decorator def foobar (self): print "adsf" ¿Hay alguna solución?
extraeee
1
@extraeee: revisa la edición que hice. debe calificar el decorador dado como un método estático, pero solo después de que haya terminado de usarlo (o de asignar la versión del método estático a un nombre diferente)
Michael Speer
58

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

class Test(object):

    def _decorator(self, foo):
        foo()

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

Por supuesto, no es válido ya selfque no está definido en ese punto. Lo mismo ocurre Testya 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 se transforma tu fragmento de decorador.

Entonces, como puede ver, acceder 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 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)
Evan Fosmark
fuente
55
probablemente debería actualizarse para hacer referencia a la respuesta más precisa a continuación
Nathan Buesgens
1
Agradable. Tu prosa dice imposible, pero tu código muestra más o menos cómo hacerlo.
Físico loco
22
import functools


class Example:

    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):
        print("METHOD")

    wrapper = staticmethod(wrapper)


e = Example()
e.method()
madjardi
fuente
1
TypeError: el objeto 'staticmethod' no es invocable
wyx
@wyx no llame al decorador. Por ejemplo, debería ser @foo, no@foo()
docyoda
¿No debería el primer argumento de wrapperser self?
CpILL
7

Utilizo este tipo de decorador en algunas situaciones de depuración, permite anular las propiedades de clase decorando, 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

Aquí está el código del 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, deberá usar @adecorator () con los corchetes para decorar funciones, incluso si no pasa ningún argumento al constructor de la clase de decorador.

Digitalacorn
fuente
7

Esta es una forma de acceder (y haber utilizado) selfdesde dentro de un decoratordefinido 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 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 funciona.

Gunnar Hermansson
fuente
4

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 el 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, 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
samwyse
fuente
4

Aquí hay una expansión de la respuesta de Michael Speer para ir 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 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 entonces

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)
Oliver Evans
fuente
3

Los decoradores parecen más adecuados para modificar la funcionalidad de un objeto completo (incluidos los objetos de función) frente a 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"

El resultado es:

>>> import Test
>>> a = Test()
>>> a.bar()
starting dec
bar
ending dec
nicodjimenez
fuente
1

Puedes decorar el decorador:

import decorator

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

    @_decorator
    def bar(self):
        pass
doep
fuente
1

Tengo una implementación de decoradores que podría ayudar

    import functools
    import datetime


    class Decorator(object):

        def __init__(self):
            pass


        def execution_time(func):

            @functools.wraps(func)
            def wrap(self, *args, **kwargs):

                """ Wrapper Function """

                start = datetime.datetime.now()
                Tem = func(self, *args, **kwargs)
                end = datetime.datetime.now()
                print("Exection Time:{}".format(end-start))
                return Tem

            return wrap


    class Test(Decorator):

        def __init__(self):
            self._MethodName = Test.funca.__name__

        @Decorator.execution_time
        def funca(self):
            print("Running Function : {}".format(self._MethodName))
            return True


    if __name__ == "__main__":
        obj = Test()
        data = obj.funca()
        print(data)
Soumil Nitin Shah
fuente
1

Declarar en clase interior. Esta solución es bastante sólida y recomendada.

class Test(object):
    class Decorators(object):
    @staticmethod
    def decorator(foo):
        def magic(self, *args, **kwargs) :
            print("start magic")
            foo(self, *args, **kwargs)
            print("end magic")
        return magic

    @Decorators.decorator
    def bar( self ) :
        print("normal call")

test = Test()

test.bar()

El resultado:

>>> test = Test()
>>> test.bar()
start magic
normal call
end magic
>>> 
Yarik Kovalchuk
fuente