101 votes

Comment décorer toutes les fonctions d'une classe sans devoir les retaper à chaque méthode ?

Disons que ma classe a beaucoup de méthodes, et je veux appliquer mon décorateur à chacune d'entre elles. Plus tard, lorsque j'ajoute de nouvelles méthodes, je veux que le même décorateur soit appliqué, mais je ne veux pas écrire @mondecorateur au-dessus de la déclaration de la méthode à chaque fois.

Si je regarde dans __call__, est-ce la bonne solution?


J'aimerais montrer cette méthode, qui est une solution similaire à mon problème pour quiconque trouverait cette question plus tard, en utilisant un mixin comme mentionné dans les commentaires.

class WrapinMixin(object):
    def __call__(self, hey, you, *args):
        print 'entrée', hey, you, repr(args)
        try:
            ret = getattr(self, hey)(you, *args)
            return ret
        except:
            ret = str(e)
            raise
        finally:
            print 'sortie', hey, repr(ret)

Ensuite, vous pouvez dans un autre

class Embrassermesmethodesautour(EnvelopperMixin): 
    def __call__:
         return super(Embrassermesmethodesautour, self).__call__(hey, you, *args)

Note de l'éditeur : cet exemple semble résoudre un problème différent de celui posé dans la question.

101voto

Décorez la classe avec une fonction qui parcourt les attributs de la classe et décore les fonctions appelables. Cela peut être la mauvaise solution si vous avez des variables de classe qui peuvent être appelables et décore également les classes imbriquées (crédit à Sven Marnach pour avoir souligné cela), mais en général c'est une solution plutôt propre et simple. Implémentation d'exemple (notez que cela n'exclura pas les méthodes spéciales (__init__ etc.), ce qui peut être souhaité ou non) :

def for_all_methods(decorator):
    def decorate(cls):
        for attr in cls.__dict__: # there's propably a better way to do this
            if callable(getattr(cls, attr)):
                setattr(cls, attr, decorator(getattr(cls, attr)))
        return cls
    return decorate

Utilisation :

@for_all_methods(mydecorator)
class C(object):
    def m1(self): pass
    def m2(self, x): pass
    ...

40voto

IfLoop Points 59461

Alors que je n'aime pas utiliser des approches magiques quand une approche explicite suffirait, vous pouvez probablement utiliser une métaclasse pour cela.

def myDecorator(fn):
    fn.foo = 'bar'
    return fn

class myMetaClass(type):
    def __new__(cls, name, bases, local):
        for attr in local:
            value = local[attr]
            if callable(value):
                local[attr] = myDecorator(value)
        return type.__new__(cls, name, bases, local)

class myClass(object):
    __metaclass__ = myMetaClass
    def baz(self):
        print self.baz.foo

et cela fonctionne comme si chaque callable dans myClass avait été décoré avec myDecorator

>>> quux = myClass()
>>> quux.baz()
bar

18voto

nickneedsaname Points 319

Ne pas ressusciter les choses du passé, mais j'ai vraiment aimé la réponse de delnan, mais j'ai trouvé qu'elle manquait légèrement.

def for_all_methods(exclude, decorator):
    def decorate(cls):
        for attr in cls.__dict__:
            if callable(getattr(cls, attr)) and attr not in exclude:
                setattr(cls, attr, decorator(getattr(cls, attr)))
        return cls
    return decorate

EDIT: corriger l'indentation

Ainsi, vous pouvez spécifier les méthodes//attributs//choses que vous ne voulez pas décorer

11voto

Sergey Orshanskiy Points 1180

Aucune des réponses ci-dessus ne fonctionnaient pour moi, car je voulais également décorer les méthodes héritées, ce qui n'était pas accompli en utilisant __dict__, et je ne voulais pas compliquer les choses avec des métaclasses. Enfin, je suis d'accord pour avoir une solution pour Python 2, car j'ai juste un besoin immédiat d'ajouter du code de profilage pour mesurer le temps utilisé par toutes les fonctions d'une classe.

import inspect
def for_all_methods(decorator):
    def decorate(cls):
        for name, fn in inspect.getmembers(cls, inspect.ismethod):
            setattr(cls, name, decorator(fn))
        return cls
    return decorate

Source (solution légèrement différente) : https://stackoverflow.com/a/3467879/1243926 Là, vous pouvez également voir comment le modifier pour Python 3.

Comme le suggèrent les commentaires des autres réponses, envisagez d'utiliser inspect.getmembers(cls, inspect.isroutine) à la place. Si vous avez trouvé une solution appropriée qui fonctionne à la fois pour Python 2 et Python 3 et qui décore les méthodes héritées, et qui peut toujours être faite en 7 lignes, veuillez éditer.

5voto

Jeremy Banks Points 32470

Vous pourriez générer une métaclasse. Cela ne décorera pas les méthodes héritées.

def decorating_meta(decorator):
    class DecoratingMetaclass(type):
        def __new__(self, class_name, bases, namespace):
            for key, value in list(namespace.items()):
                if callable(value):
                    namespace[key] = decorator(value)

            return type.__new__(self, class_name, bases, namespace)

    return DecoratingMetaclass

Cela générera une métaclasse décorant toutes les méthodes avec la fonction spécifiée. Vous pouvez l'utiliser en Python 2 ou 3 en faisant quelque chose comme ceci

def doubling_decorator(f):
    def decorated(*a, **kw):
        return f(*a, **kw) * 2
    return decorated

class Foo(dict):
    __metaclass__ = decorating_meta(doubling_decorator)

    def lookup(self, key):
        return self[key]

d = Foo()
d["bar"] = 5
print(d.lookup("bar")) # imprime 10

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X