I have a module that has a function whose prototype is similar to that of the thread class.
def do(fn, argtuple=(), kwargdict={}, priority=0,
block=False, timeout=0, callback=None, daemon=False)
# do stuff
fn is a callable, and argtuple and kwargdict are positional and dictionary arguments that will be passed to the fn callable when it gets called.
I'm now trying to write a decorator for this, but I'm confused. I've never really had a very good grasp on decorators. Is there a way to make a decorator that I can set the options in the above such as timeout, but pass in argtuple and kwargdict when the function is called.
So for example:
@do(priority=2)
def decoratedTask(arg, dic=3):
#do stuff
decoratedTask(72)
I'm confused how I would pass the runtime argument 72 into the decorated function. I'm thinking the decorator needs to be a class where the __call__
method returns the function call, but I'm unsure the syntax of how to pass in arguments like that.
Does this make sense?
The decorator arguments are accessible to the inner decorator through a closure, exactly like how the wrapped() inner function can access f . And since closures extend to all the levels of inner functions, arg is also accessible from within wrapped() if necessary.
Decorator Functions with Decorator ArgumentsThe return value of the decorator function must be a function used to wrap the function to be decorated. That is, Python will take the returned function and call it at decoration time, passing the function to be decorated.
These fancy symbols are called decorators. Python decorators represent a form of metaprogramming. This means they have the ability to modify another part of the program while the code is running. In simple terminology, they are functions that take other functions as arguments.
What the decorator does is that it takes the function as an argument and also returns a function, typically a new function that is created in the decorator.
That new function needs to take the same parameters as the function you decorate, and it also needs to call the original function.
Now, when you have a decorator with an argument, there is an extra level going on. That decorator should take the argument, and return a decorator. The function you use is in fact not a decorator, but a decoratormaker!
Here is an example:
>>> def mydeco(count):
... def multipass(fn):
... def caller(*args, **kw):
... return [fn(*args, **kw) for x in range(count)]
... return caller
... return multipass
...
>>> @mydeco(5)
... def printer(text):
... print(text)
...
>>> printer("Yabbadabbadoo!")
Yabbadabbadoo!
Yabbadabbadoo!
Yabbadabbadoo!
Yabbadabbadoo!
Yabbadabbadoo!
[None, None, None, None, None]
You'll see examples of these decoratormakers implemented as classes. That how I prefer it too (although I usually end up not having a decorator at all anyway), but a function in a function in a function works. :)
That's not quite how the decorator syntax works. When you write @do(priority=2)
, Python will evaluate do(priority=2)
and use the result of that call as the decorator. It's shorthand for
decorator=do(priority=2)
@decorator
So you actually want to make do
a decorator factory: you want it to take all the positional arguments and return a decorator.
def do(args=(), kwargs={}, ...):
def _decorator(fn):
def newfn(*args, **kwargs):
return fn(*args, **kwargs)
return newfn
return _decorator
Notice that there are actually three functions here!
do
is the function that we call do get our decorator (so e.g. do(priority=2)
is an example decorator)_decorator
is the actual decorator returned, which depends on the arguments to do
newfn
is that function.Example:
>>> def rename(name):
... def _decorator(fn):
... def renamed(*args, **kwargs):
... return fn(*args, **kwargs)
... renamed.__name__ = name
... return renamed
... return _decorator
...
>>> @rename('I like omelettes in the morning.')
... def foo():
... return 'bar'
...
>>> foo()
'bar'
>>> foo.__name__
'I like omelettes in the morning.'
or equivalently
>>> omeletter = rename('I like omelettes in the morning.')
>>> @omeletter
... def foo():
... return 'bar'
...
>>> foo()
'bar'
>>> foo.__name__
'I like omelettes in the morning.'
By the way, take care with the mutable default arguments ()
and {}
; I'm sure you know the perils!
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