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.
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
.
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
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