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)