I've seen many examples of Python decorators that are:
__init__
, __get__
, and __call__
)But I've never seen a single example which can do all of the above, and I'm having trouble synthesizing from various answers to specific questions (such as this one, this one, or this one (which has one of the best answers I've ever seen on SO)), how to combine all of the above.
What I want is a class-based decorator which can decorate either a method or a function, and that takes at least one additional parameter. Ie so that the following would work:
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()
And I would expect to see:
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: if it matters, I'm using Python 2.7.
A decorator in Python is any callable Python object that is used to modify a function or a class. A reference to a function "func" or a class "C" is passed to a decorator and the decorator returns a modified function or class.
The winning syntax as of now uses the '@' symbol, as described in this message. Mark Russell implemented this version. Here is the message describing the patch he checked in. There has been a long discussion about the syntax to use for decorators in Python.
The syntax for decorators with parameters :As the execution starts from left to right decorator(params) is called which returns a function object fun_obj.
To decorate a method in a class, first use the '@' symbol followed by the name of the decorator function. A decorator is simply a function that takes a function as an argument and returns yet another function.
You don't need to mess around with descriptors. It's enough to create a wrapper function inside the __call__()
method and return it. Standard Python functions can always act as either a method or a function, depending on context:
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 result = fn(*args, **kwargs) print "In my decorator after call, with arg %s" % self.arg return result return decorated
A bit of explanation about what's going on when this decorator is used like this:
@MyDecorator("some other func!") def some_other_function(): print "in some other function!"
The first line creates an instance of MyDecorator
and passes "some other func!"
as an argument to __init__()
. Let's call this instance my_decorator
. Next, the undecorated function object -- let's call it bare_func
-- is created and passed to the decorator instance, so my_decorator(bare_func)
is executed. This will invoke MyDecorator.__call__()
, which will create and return a wrapper function. Finally this wrapper function is assigned to the name some_other_function
.
You're missing a level.
Consider the code
class Foo(object): @MyDecorator("foo baby!") def bar(self): print "in bar!"
It is identical to this code
class Foo(object): def bar(self): print "in bar!" bar = MyDecorator("foo baby!")(bar)
So MyDecorator.__init__
gets called with "foo baby!"
and then the MyDecorator
object gets called with the function bar
.
Perhaps you mean to implement something more like
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With