web-dev-qa-db-fra.com

Comment décorer toutes les fonctions d'une classe sans la taper encore et encore pour chaque méthode?

Disons que ma classe a de nombreuses méthodes, et je veux appliquer mon décorateur sur chacune d'entre elles, plus tard lorsque j'ajouterai de nouvelles méthodes, je veux que le même décorateur soit appliqué, mais je ne veux pas écrire @mydecorator au-dessus de la déclaration de méthode all le temps?

Si je regarde dans __call__ est-ce la bonne façon de procéder?

IMPORTANT: l'exemple ci-dessous semble résoudre un problème différent de la question d'origine posée.

EDIT: Je voudrais montrer cette façon, qui est une solution similaire à mon problème pour quiconque trouve cette question plus tard, en utilisant un mixin comme mentionné dans les commentaires.

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

Ensuite, vous pouvez dans un autre

class Wrapmymethodsaround(WrapinMixin): 
    def __call__:
         return super(Wrapmymethodsaround, self).__call__(hey, you, *args)
51
rapadura

Décorez la classe avec une fonction qui parcourt les attributs de la classe et décore les callables. Ce n'est peut-être pas la bonne chose à faire si vous avez des variables de classe qui peuvent être appelables et qui décoreront également les classes imbriquées (remerciements à Sven Marnach pour l'avoir signalé), mais généralement c'est une solution plutôt propre et simple. Exemple d'implémentation (notez que cela n'exclura pas les méthodes spéciales (__init__ Etc.), qui peuvent ou non être souhaitées):

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

Utilisez comme ceci:

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

En Python 3.0 et 3.1, callable n'existe pas. Il existait depuis toujours en Python 2.x et est de retour en Python 3.2 comme wrapper pour isinstance(x, collections.Callable), vous pouvez donc l'utiliser (ou définir votre propre remplacement callable en l'utilisant) dans ces versions.

55
user395760

Bien que je n'aime pas utiliser des approches magiques lorsqu'une approche explicite le ferait, 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 appelable dans myClass avait été décoré avec myDecorator

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

Pas pour faire revivre les choses d'entre les morts, mais j'ai vraiment aimé la réponse de Delnan, mais je l'ai trouvée manquante sllliigghhtttlllyy.

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: correction du retrait

Vous pouvez donc spécifier des méthodes // attributs // des choses que vous ne voulez pas décorer

8
nickneedsaname

Aucune des réponses ci-dessus n'a fonctionné pour moi, car je voulais également décorer les méthodes héritées, ce qui n'a pas été accompli en utilisant __dict__, Et je ne voulais pas trop compliquer les choses avec des métaclasses. Enfin, je suis bien d'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 changer pour Python 3.

Comme le suggèrent les commentaires des autres réponses, envisagez plutôt d'utiliser inspect.getmembers(cls, inspect.isroutine). Si vous avez trouvé une solution appropriée qui fonctionne pour les deux Python 2 et Python 3 et décore les méthodes héritées, et peut toujours être fait en 7 lignes, s'il vous plaît, Éditer.

4
osa

Vous pouvez générer une métaclasse. Cela ne décore 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 dans Python 2 ou 3 en faisant quelque chose comme ça

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")) # prints 10
2
Jeremy Banks