Default arguments in a function when using decorators

Question:

I cannot really tell how to use *args and **kwargs when combined with decorators and default arguments. Let me give you a MWE.

def outer(f):
    def inner(*args, **kwargs):
        print("args:", args)
        print("kwargs:", kwargs)

    return inner

@outer
def simple(x, y):
    pass

Running the simple function passing arguments in a different format. So:

Running:

simple(10, 20)

args: (10, 20)
kwargs: {}

Running:

simple(x=10, y=20)

args: ()
kwargs: {'x': 10, 'y': 20}

Running:

simple(10, y=20)

args: (10,)
kwargs: {'y': 20}

All of these seem to be expected. What I cannot understand is if I define the simple function with a default value something like:

@outer
def simple(x, y=100):
    pass

If I then run simple(10) I would have expected the outcome to be:

args: (10,)
kwargs: {'y': 100}

but I get

args: (10,)
kwargs: {}

instead. Probably my understanding is not that good, so how could I achieve the expected result?

Asked By: thanasissdr

||

Answers:

In this case **kwargs is not about the function signature, it’s about how you called it.
And your call

simple(10)

have only specified one positional argument.

Decorator (wrapper) knows nothing about the function and its default arguments.
It just passes the arguments it received further.

simple(*(10,), **{})

If you want to do some excercise and write decorator that is informed about the defaults of a function it is wrapping, I suggest to take a look at inspect.signature().

Answered By: madbird

Facing the same issue and following @madbird answer, here’s a snippet of a decorator that is aware of the kwargs defaults for the function it wraps:

def outer(f):

    signature = inspect.signature(f)

    default_kwargs = {
        kw: value.default for kw, value in signature.parameters.items() if value.default != inspect.Signature.empty
    }

    @wraps(f)
    def inner(*args, **kwargs):
        print("args:", args)
        print("kwargs:", default_kwargs | kwargs)

    return inner

It inspects the signature of the func it wraps, extracts all parameters with a default value (those whose default isn’t a Signature.empty) to a dict and then applies it with the provided kwargs of the wrapped function in-order.

(Tested in Python 3.9)

Answered By: mati.o
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.