Décorateurs Python dans les classes


140

Peut-on écrire quelque chose comme:

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

    @self._decorator
    def bar(self):
        pass

Cela échoue: le soi dans @self est inconnu

J'ai aussi essayé:

@Test._decorator(self)

qui échoue également: Test inconnu

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

Réponses:


268

Quelque chose comme ça ferait 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 l'appel à soi pour accéder au décorateur et le laisse caché dans l'espace de noms de classe comme une méthode régulière.

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

modifié 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

Production:

Normal:
start magic
normal call
end magic

Inherited:
start magic
override bar in
start magic
normal call
end magic
override bar out
end magic

7
Le décorateur ou la fonction décorée? Notez que la fonction "magic" retournée qui encapsule la barre reçoit une auto-variable ci-dessus lorsque "bar" est appelé sur une instance et pourrait faire n'importe quoi aux variables d'instance qu'elle voulait avant et après (ou même si oui ou non) elle a appelé "bar" . Il n'y a pas de variables d'instance lors de la déclaration de la classe. Vouliez-vous faire quelque chose à la classe à partir du décorateur? Je ne pense pas que ce soit un usage idiomatique.
Michael Speer

1
Merci Michael, je viens de voir que c'est ce que je voulais.
hcvst

2
Je trouve cette solution beaucoup plus agréable que la réponse acceptée, car elle permet l'utilisation de la syntaxe @ decorator au point de définition. Si je dois mettre des appels de décorateur à la fin du cours, il n'est pas clair que les fonctions soient décorées. C'est un peu bizarre que vous ne puissiez pas utiliser @staticmethod sur le décorateur lui-même, mais au moins cela fonctionne.
mgiuca

1
Je ne pense pas que cela fonctionne si je crée une classe héritée de Test.Par exemple: class TestB (Test): @_decorator def foobar (self): print "adsf" Y a-t-il une solution de contournement?
extraeee

1
@extraeee: vérifiez la modification que j'ai faite. vous devez qualifier le décorateur donné comme une méthode statique, mais seulement après avoir fini de l'utiliser (ou d'attribuer la version de la méthode statique à un nom différent)
Michael Speer

58

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

class Test(object):

    def _decorator(self, foo):
        foo()

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

Cela, bien sûr, n'est pas valide car selfn'est pas défini à ce stade. Il en va de même Testcar elle ne sera pas définie tant que la classe elle-même ne sera pas définie (dont elle est en train de l'être). Je vous montre cet extrait de code, car c'est ce que votre extrait de code de décorateur transforme.

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

Si vous avez besoin d' un accès au niveau de la classe , essayez ceci:

class Test(object):

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

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

5
devrait probablement être mis à jour pour faire référence à la réponse plus précise ci
Nathan Buesgens

1
Agréable. Votre prose dit impossible, mais votre code montre à peu près comment le faire.
Mad Physicist

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

1
TypeError: l'objet 'staticmethod' n'est pas appelable
wyx

@wyx n'appelle pas le décorateur. Par exemple, ça devrait être @foo, pas@foo()
docyoda le

Le premier argument ne devrait-il pas wrapperêtre self?
CpILL

7

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

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: parce que j'ai utilisé un décorateur de classe, vous devrez utiliser @adecorator () avec les crochets pour décorer les fonctions, même si vous ne passez aucun argument au constructeur de la classe du décorateur.


7

C'est une façon d'accéder (et d'avoir utilisé) selfdepuis l'intérieur d'un decoratordéfini à l'intérieur de 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 idiot, mais cela fonctionne.


4

J'ai trouvé cette question en recherchant un problème très similaire. Ma solution est de diviser 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 au 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é certaines 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

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

Puis

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

Les décorateurs semblent mieux adaptés pour modifier la fonctionnalité d'un objet entier (y compris les objets de fonction) par rapport à la fonctionnalité d'une méthode d'objet qui dépendra 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

1

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

J'ai une implémentation de décorateurs qui pourraient aider

    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)

1

Déclarez en classe interne. Cette solution est assez solide et recommandée.

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

Le résultat:

>>> test = Test()
>>> test.bar()
start magic
normal call
end magic
>>> 
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.