Décorateur basé sur une classe Python avec des paramètres qui peuvent décorer une méthode ou une fonction

J'ai vu de nombreux exemples de décorateurs Python qui sont:

  • décorateurs de style de fonction (envelopper une fonction)
  • décorateurs de style de classe (implémentation __init__, __get__, et __call__)
  • décorateurs qui ne prennent pas d'arguments
  • décorateurs qui prennent des arguments
  • décorateurs qui sont "method friendly" (ie peut décorer une méthode dans une classe)
  • décorateurs qui sont "function friendly" (peut décorer une fonction simple
  • décorateurs qui peuvent décorer les méthodes et les fonctions

Mais je n'ai jamais vu un seul exemple qui peut faire tout ce qui précède, et j'ai du mal à synthétiser à partir de diverses réponses à des questions spécifiques (comme celui-ci, celui-ci , ou celui-ci (qui a L'une des meilleures réponses que j'ai jamais vu sur SO)), comment combiner tout ce qui précède.

Ce que je veux, c'est un la classe de base décorateur qui peut décorer soit une méthode ou d'un la fonction, et cela prend au moins un paramètre supplémentaire. Ie de sorte que ce qui suit fonctionnerait:

class MyDecorator(object):
    def __init__(self, fn, argument):
        self.fn = fn
        self.arg = argument

    def __get__(self, ....):
        # voodoo magic for handling distinction between method and function here

    def __call__(self, *args, *kwargs):
        print "In my decorator before call, with arg %s" % self.arg
        self.fn(*args, **kwargs)
        print "In my decorator after call, with arg %s" % self.arg


class Foo(object):
    @MyDecorator("foo baby!")
    def bar(self):
        print "in bar!"


@MyDecorator("some other func!")
def some_other_function():
    print "in some other function!"

some_other_function()
Foo().bar()

Et je m'attendrais à voir:

In my decorator before call, with arg some other func!
in some other function!
In my decorator after call, with arg some other func!
In my decorator before call, with arg foo baby!
in bar!
In my decorator after call, with arg foo baby!

Edit: si c'est important, j'utilise python 2.7.

35
demandé sur Community 2012-02-23 20:22:02

3 réponses

Vous n'avez pas besoin de jouer avec des descripteurs. Il suffit de créer une fonction wrapper dans la méthode __call__() et de la renvoyer. Les fonctions Python Standard peuvent toujours agir comme une méthode ou une fonction, selon le contexte:

class MyDecorator(object):
    def __init__(self, argument):
        self.arg = argument

    def __call__(self, fn):
        @functools.wraps(fn)
        def decorated(*args, **kwargs):
            print "In my decorator before call, with arg %s" % self.arg
            fn(*args, **kwargs)
            print "In my decorator after call, with arg %s" % self.arg
        return decorated

Un peu d'explication sur ce qui se passe quand ce décorateur est utilisé comme ceci:

@MyDecorator("some other func!")
def some_other_function():
    print "in some other function!"

La première ligne crée une instance de MyDecorator et passe "some other func!" comme un argument de __init__(). Appelons cette instance my_decorator. Ensuite, le non décoré function object -- appelons-le bare_func -- est créé et passé à l'instance de décorateur, donc my_decorator(bare_func) est exécuté. Cela appellera MyDecorator.__call__(), qui créera et retournera une fonction wrapper. Enfin, cette fonction wrapper est affectée au nom some_other_function.

32
répondu Sven Marnach 2012-02-23 16:47:07

Il te manque un niveau.

Considérez le code

class Foo(object):
    @MyDecorator("foo baby!")
    def bar(self):
        print "in bar!"

, Il est identique à ce code

class Foo(object):
    def bar(self):
        print "in bar!"
    bar = MyDecorator("foo baby!")(bar)

, Donc MyDecorator.__init__ est appelée avec "foo baby!", puis le MyDecorator objet est appelée à la fonction bar.

Peut-être voulez-vous implémenter quelque chose de plus comme

import functools

def MyDecorator(argument):
    class _MyDecorator(object):
        def __init__(self, fn):
            self.fn = fn

        def __get__(self, obj, type=None):
            return functools.partial(self, obj)

        def __call__(self, *args, **kwargs):
            print "In my decorator before call, with arg %s" % argument
            self.fn(*args, **kwargs)
            print "In my decorator after call, with arg %s" % argument

    return _MyDecorator
10
répondu Mike Graham 2016-03-10 05:00:01

Dans votre liste de types de décorateurs, vous avez manqué des décorateurs qui peuvent ou non prendre des arguments. Je pense que cet exemple couvre tous vos types sauf "décorateurs de style de fonction (enveloppant une fonction)"

class MyDecorator(object):

    def __init__(self, argument):
        if hasattr('argument', '__call__'):
            self.fn = argument
            self.argument = 'default foo baby'
        else:
            self.argument = argument

    def __get__(self, obj, type=None):
        return functools.partial(self, obj)

    def __call__(self, *args, **kwargs):
        if not hasattr(self, 'fn'):
            self.fn = args[0]
            return self
        print "In my decorator before call, with arg %s" % self.argument
        self.fn(*args, **kwargs)
        print "In my decorator after call, with arg %s" % self.argument


class Foo(object):
    @MyDecorator("foo baby!")
    def bar(self):
        print "in bar!"

class Bar(object):
    @MyDecorator
    def bar(self):
        print "in bar!"

@MyDecorator
def add(a, b):
    print a + b
4
répondu Vinayak Kaniyarakkal 2017-09-22 09:53:29