Decorator that can accept both init args and call args?
Is it possible to create a decorator that can be __init__
'd with a bunch of arguments and then methods called with other arguments?
For instance:
from foo import MyDecorator
bar = MyDecorator(debug=True)
@bar.myfunc(a=100)
def spam():
pass
@bar.myotherfunc(x=False)
def eggs():
pass
If possible, can you provide a working example?
a source to share
Of course, a decorator is just a function that takes a function and returns a function. There is no reason a function cannot be (or, if you have arguments, cannot be returned) an instance method. Here's a really trivial example (because I'm not sure what exactly you are trying to do with this):
class MyDecorator(object):
def __init__(self, debug):
self.debug = debug
def myfunc(self, a):
def decorator(fn):
def new_fn():
if self.debug:
print a
fn()
return new_fn
return decorator
def myotherfunc(self, x):
def decorator(fn):
def new_fn():
if self.debug:
print x
fn()
return new_fn
return decorator
As I said, I can't think of an option for this out of my head. But I'm sure they are there.
a source to share
To do this, you need another layer of wrapping, using a closure, for example:
import functools
def say_when_called(what_to_say):
def decorator(fn):
@functools.wraps(fn)
def wrapper(*args, **kw):
print what_to_say
return fn(*args, **kw)
return wrapper
return decorator
@say_when_called("spam")
def my_func(v):
print v
my_func("eggs")
Output:
spam
eggs
(see http://codepad.org/uyJV56gk )
Note that I used functools.wraps
here to make the decorated function look like the original. This is not functionally necessary, but a good thing to do if your code reads attributes __name__
or __doc__
your function.
Class based example:
class SayWhenCalledWrapper(object):
def __init__(self, fn, what_to_say):
self.fn = fn
self.what_to_say = what_to_say
def __call__(self, *args, **kw):
print self.what_to_say
return self.fn(*args, **kw)
class SayWhenCalled(object):
def __init__(self, what_to_say):
self.what_to_say = what_to_say
def __call__(self, fn):
return SayWhenCalledWrapper(fn, self.what_to_say)
@SayWhenCalled("spam")
def my_func(v):
print v
my_func("eggs")
Output:
spam
eggs
(see http://codepad.org/6Y2XffDN )
a source to share
the property decorator looks something like this. @property
decorates a function and replaces it with an object that has getter, setter and deleter functions that are decorators as well.
It's a bit more complicated than the OP's example because there are two levels of decoration, but the principle is the same.
a source to share
Huin's answer is very good. Its two variants execute decorator code only when a certain function is defined (this is not a criticism, how often this is exactly what you want). Here's an extension of his class-based approach, which also executes every code on every function call. This is what you do, for example, when you use decorators for thread safety.
class MyInnerDecorator:
def __init__( self, outer_decorator, *args, **kwargs ):
self._outerDecorator = outer_decorator
self._args = args
self._kwargs = kwargs
def __call__( self, f ):
print "Decorating function\n"
self._f = f
return self.wrap
def wrap( self, *args, **kwargs ):
print "Calling decorated function"
print "Debug is ", self._outerDecorator._debug
print "Positional args to decorator: ", self._args
print "Keyword args to decorator: ", self._kwargs
print "Positional args to function call: ", args
print "Keyword args to function call: ", kwargs
return self._f( *args, **kwargs )
print "\n"
class MyDecorator:
def __init__( self, debug ):
self._debug = debug
def myFunc( self, *args, **kwargs ):
return MyInnerDecorator( self, "Wrapped by myFunc", *args, **kwargs )
def myOtherFunc( self, *args, **kwargs ):
return MyInnerDecorator( self, "Wrapped by myOtherFunc", *args, **kwargs )
bar = MyDecorator( debug=True )
@bar.myFunc( a=100 )
def spam( *args, **kwargs ):
print "\nIn spam\n"
@bar.myOtherFunc( x=False )
def eggs( *args, **kwargs ):
print "\nIn eggs\n"
spam( "penguin" )
eggs( "lumberjack" )
Which outputs this:
Decorating function
Decorating function
Calling decorated function
Debug is True
Positional args to decorator: ('Wrapped by myFunc',)
Keyword args to decorator: {'a': 100}
Positional args to function call: ('penguin',)
Keyword args to function call: {}
In spam
Calling decorated function
Debug is True
Positional args to decorator: ('Wrapped by myOtherFunc',)
Keyword args to decorator: {'x': False}
Positional args to function call: ('lumberjack',)
Keyword args to function call: {}
In eggs
a source to share