How can I pass arguments to decorator, process there, and forward to decorated function?
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.
Functions are objects:
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).
Decorators:
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)