web-dev-qa-db-fra.com

Python décorateurs dans les classes

Peut-on écrire quelque chose comme:

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

    @self._decorator
    def bar(self):
        pass

Cela échoue: self in @self est inconnu

J'ai aussi essayé:

@Test._decorator(self)

qui échoue également: Test unknown

Je souhaite modifier temporairement certaines variables d'instance dans le décorateur, puis exécuter la méthode décorée, avant de les rétablir.

121
hcvst

Quelque chose comme ça ferait-il ce dont vous avez besoin?

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()

Cela évite que l’appel à soi-même accède au décorateur et le laisse caché dans l’espace de noms de classe en tant que méthode normale.

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

édité pour répondre à la question dans les commentaires:

Comment utiliser le décorateur caché dans une autre classe

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

Sortie:

Normal:
start magic
normal call
end magic

Inherited:
start magic
override bar in
start magic
normal call
end magic
override bar out
end magic
233
Michael Speer

Ce que vous voulez faire n'est pas possible. Prenez, par exemple, si le code ci-dessous semble ou non valide:

class Test(object):

    def _decorator(self, foo):
        foo()

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

Bien entendu, il n’est pas valide puisque self n’est pas défini à ce stade. Il en va de même pour Test car elle ne sera définie que lorsque la classe elle-même sera définie (ce qui est en cours de). Je vous montre cet extrait de code car c'est en cela que se transforme votre extrait de décorateur.

Ainsi, comme vous pouvez le constater, accéder à l'instance dans un décorateur comme cela n'est pas vraiment possible, car les décorateurs sont appliqués lors de la définition de la fonction/méthode à laquelle ils sont attachés et non lors de l'instanciation.

Si vous avez besoin de accès de niveau classe, essayez ceci:

class Test(object):

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

    def bar(self):
        pass
Test.bar = Test._decorator(Test.bar)
54
Evan Fosmark
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()
17
madjardi

C’est un moyen que je connais (et que j’ai utilisé) d’accéder à self depuis un décorateur défini dans la même classe:

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

Sortie (testé sur 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'

L'exemple ci-dessus est stupide, mais montre que cela fonctionne.

7

J'utilise ce type de décorateur dans certaines situations de débogage, cela permet de redéfinir les propriétés de classe en décorant, sans avoir à trouver la fonction d'appel.

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

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

Voici le code du décorateur

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 

Remarque: comme j'ai utilisé un décorateur de classe, vous devrez utiliser @adecorator () avec les crochets on pour décorer les fonctions, même si vous ne transmettez aucun argument au constructeur de la classe de décorateur.

6
digitalacorn

J'ai trouvé cette question en recherchant un problème très similaire. Ma solution consiste à scinder le problème en deux parties. Tout d'abord, vous devez capturer les données que vous souhaitez associer aux méthodes de classe. Dans ce cas, handler_for associera une commande Unix à un gestionnaire pour la sortie de cette commande.

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

Maintenant que nous avons associé des données à chaque méthode de classe, nous devons rassembler ces données et les stocker dans un attribut de classe.

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

Voici une extension de la réponse de Michael Speer pour aller plus loin:

Un décorateur de méthode d'instance qui prend des arguments et agit sur une fonction avec des arguments et une valeur de retour.

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

Et alors

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
Oliver Evans

Les décorateurs semblent plus aptes à modifier la fonctionnalité d'un objet entier (y compris les objets de fonction) par rapport à la fonctionnalité d'un méthode d'objet qui dépend en général des attributs d'instance. Par exemple:

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 sortie est:

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

Vous pouvez décorer le décorateur:

import decorator

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

    @_decorator
    def bar(self):
        pass
1
doep