trpt4him
trpt4him

Reputation: 1906

Keyword arguments for function wrapped with decorator not working

All the tutorials I find on decorators suggest using *args, **kwargs in the wrapping function's signature, to handle arguments from the wrapped function. Yet kwargs are not working, while plain args are:

from functools import wraps


def wrapper(a_thing):
    @wraps(a_thing)
    def do_thing(*args, **kwargs):
        print('before')
        print(kwargs)
        value = a_thing(*args, **kwargs)
        print("after", *args, **kwargs)
        return value
    return do_thing


@wrapper
def output(*args, **kwargs):
    print('during', *args, **kwargs)

import pdb; pdb.set_trace()

Here's my interactive output:

(Pdb) output(99, 100)
before
{}
during 99 100
after 99 100
(Pdb) output(arg1=99, arg2=100)
before
{'arg1': 99, 'arg2': 100}
*** TypeError: 'arg1' is an invalid keyword argument for this function
(Pdb)

Here's an example of one of many such tutorials:

What's the point of using **kwargs if it doesn't work? I feel like I'm missing something.

Upvotes: 2

Views: 245

Answers (1)

ShadowRanger
ShadowRanger

Reputation: 155477

You don't want to unpack in the calls to print; print only accepts a limited number of keyword arguments, and will reject all others. Presumably you just want to see what was passed, so print the raw tuple and dict without unpacking:

def wrapper(a_thing):
    @wraps(a_thing)
    def do_thing(*args, **kwargs):
        print('before')
        print(kwargs)
        value = a_thing(*args, **kwargs)
        print("after", args, kwargs)  # Removed unpacking
        return value
    return do_thing


@wrapper
def output(*args, **kwargs):
    print('during', args, kwargs)  # Removed unpacking

The point of unpacking is that it passes the elements of a * unpacked iterable as sequential positional arguments, and the key value pairs from a ** unpacked mapping using the keys as keyword arguments, and the values as the associated value. So when you did:

    print('during', *args, **kwargs)

having called into the function with output(arg1=99, arg2=100), it was as if you ran:

    print('during', arg1=99, arg2=100)

Neither arg1 nor arg2 are keywords accepted by print, so it screamed at you. By removing the unpacking, the print becomes equivalent to:

    print('during', (), {'arg1': 99, 'arg2': 100})

which is perfectly valid (it's printing the tuple and dict directly, not trying to unpack).

Upvotes: 2

Related Questions