I recently decided to learn Python decorators, and followed this howto. Especially the exemple under the section "passing arguments to decorators". What I want to do is (1) the decorator function should accept some arguments (decarg), (2) process them, and (3) call the decorated function with arguments (funarg). I see how to give arguments to the decorator, but I haven't seen any example, where the arguments of the decorated function become available only in the decorator. When I call the decorated function, I completely not sure whit what arguments I should do it, as its arguments are to be calculated in the decorator function. Here is an example based on the mentioned howto:
def dec(decarg):
def _dec(fun):
funarg = decarg + 7
def _fun(funarg):
return fun(funarg)
return _fun
return _dec
def main(mainarg):
decarg = mainarg + 2
@dec(decarg)
def fun1(funarg):
return funarg + 3
return fun1(decarg)
main(1)
This returns 6, when I expect 13. I want the argument incremented by 2 in the main()
, and by 7 in _dec()
, which should pass this variable to the decorated function, which adds 3 to it.
After, I read this and this howtos, and using their approach I created an example which works as I imagined:
class dec(object):
def __init__(self, fun):
self.fun = fun
def __call__(self, decarg):
funarg = decarg + 7
return self.fun(funarg)
def main(mainarg):
decarg = mainarg + 2
@dec
def fun1(funarg):
return funarg + 3
return fun1(decarg)
main(1)
So now my question is: how to do the same with the first type of notation, where the decorator is not a class, but a function? Could you please clarify, how it works, which arguments and when are to be passed to the __init__()
and what to the __call__()
method of the decorator?
To make matters more confusing, you can also see that the wrapped() inner function has "catch-all" arguments which are passed directly into the decorated function and should never be mixed with arguments meant for the decorator. So there is really no place where decorator arguments can be added.
You can pass data to functions so they can work on that data. For example, you can create a function named adder() that you want to add two integers and display the results. Now in the body of the function, you can refer to the first argument as x and the second argument as y.
Dynamic Function Arguments We simply can make solve_for() accept *args and **kwargs then pass that to func() . Of course, you will need to handle the arguments in the function that will be called.
The reason is immediate after considering how the decorator transforms the function and that functions are objects themselves in Python.
Let's start from the latter.
This is immediate when we consider the meaning of two pairs of parenthesis after a function name. Consider this simple example (Python 3):
def func(x):
def func2(y):
return x + y + 1
return func2
result = func(5)(10)
print(result) # 15
Here "func" returns a function object "func2" and therefore you can use:
func(5)(10)
You can view this as calling first
func(5)
and applying "(10)" to the resulting object that is a function! So you have:
func2(10)
Now since both "x" and "y" are defined, "func2" can return the final value to "result".
Remember, this is all possible because functions are object themselves and because "func" returns a function object
func2
and not its result (it is not invoking the function on its own)
func2()
In short, that means that with wrapped functions the second set of arguments is for the inner function (if the wrapper returns the inner function object).
In your example, "main" calls "fun1" in the last line with
return fun1(decarg)
Due to the decorator
@dec(decarg)
In reality you can think of "fun1" as:
fun1 = dec(decarg)(fun1)
Therefore, the last line in "main" is equivalent to:
return dec(decarg)(fun1)(decarg)
With the previous explanation it should be trivial to find the problem!
dec(decarg)
gets executed and returns a "_dec" function object; note that this "decarg" is the one passed in the first parenthesis and thus in the decorator._dec(fun1)
gets executed and returns a "_fun" function object._fun(decarg)
gets executed and invokes fun1(decargs) with the return statement and this will correctly translate in fun1(3) that is the result you get; note that this "decarg" is the one passed in the third parenthesis and thus when you invoke "fun1" in main.You don't get 13 as a result because you don't invoke "fun1" with the result from
funarg = decarg + 7
as argument, but rather you invoke it with "decarg" that is passed to "_fun" as positional argument (funarg=decarg) from main.
Anyway, I have to thank you for this question, because I was looking for a neat way to pass an argument to a decorator only when invoking a function, and this works very nicely.
Here is another example that might help:
from functools import wraps
def add(addend):
def decorator(func):
@wraps(func)
def wrapper(p1, p2=101):
for v in func(p1, p2):
yield v + addend
return wrapper
return decorator
def mul(multiplier):
def decorator(func):
@wraps(func)
def wrapper(p1, p2=101):
for v in func(p1, p2):
yield v * multiplier
return wrapper
return decorator
def super_gen(p1, p2=101, a=0, m=1):
@add(a)
@mul(m)
def gen(p1, p2=101):
for x in range(p1, p2):
yield x
return gen(p1, p2)
Ok here is a simple explaination:
A simple decorator (no parameters) is a function that takes a function as argument and returns another function that will be called in place of the original one
A decorator that accepts a parameter is a function that takes that parameter and returns a simple decorator
Here you can build a simple decorator that adds 7 to the parameter of the decorated function:
def add7(func):
'''takes a func and returns a decorated func adding 7 to its parameter'''
def resul(x):
return func(x + 7)
return resul
You can use it that way:
def main(mainarg):
decarg = mainarg + 2
@add7
def fun1(funarg):
return funarg+3
return fun1(decarg)
main(1)
it returns as expected 13.
But you can easily build a parameterized decorator that will add an arbitrary value, but adding a level for processing the parameter:
def adder(incr):
'''Process the parameter (the increment) and returns a simple decorator'''
def innerdec(func):
'''Decorator that takes a function and returns a decorated one
that will add the passed increment to its parameter'''
def resul(val):
return func(val + incr)
return resul
return innerdec
You will then use it that way
def main(mainarg):
decarg = mainarg + 2
@adder(7)
def fun1(funarg):
return funarg + 3
return fun1(decarg)
main(1)
still returns 13
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