Saif I
Saif I

Reputation: 91

python decorators *args and ** kwargs

I'm brand new to coding and I've been trying to absorb as much as possible. I don't understand a lot of the technical explanations you guys post, so please try to keep it in simple English. I get the mechanics of how a decorator function works, but my issue is following the code logic - specifically why we have to add *args and ** kwargs. Is it correct to state that whatever we pass in to the decorator function that takes in a function which has arguments, will always pass the same arguments into the wrapper function because it's nested within the decorator? That's what I'm missing here. I don't understand how the arguments from the original function get passed in.

Upvotes: 8

Views: 8197

Answers (1)

abarnert
abarnert

Reputation: 365717

Let's take a simple example:

def tracing(func):
    @functools.wraps
    def wrapper(*args, **kwargs):
        logging.debug(f'Calling {func.__name__}')
        try:
            return func(*args, **kwargs)
        finally:
            logging.debug(f'Called {func.__name__}')
    return wrapper

@tracing
def spam():
    print('spam')

@tracing
def add3(n):
    return n+3

You're right that the reason we need to take *args, **kwargs is so that we can pass that same *args, **kwargs on to the wrapped function.

This is called "forwarding", or "perfect forwarding". The idea is that tracing doesn't have to know anything about the function it's wrapping—it could take any set of positional and keyword arguments, and return anything, and the wrapper still works.


For some decorators, that isn't appropriate. For example, a decorator that's designed to cache a set of functions that all have the same API, using one specific parameter as the cache key, could use *args, **kwargs and then munge through the list and dict to find that specific parameter, but it's a lot simpler, and cleaner, to be explicit:

def caching_spam(func):
    cache = {}
    @functool.wraps
    def wrapper(eggs, beans, spam, cheese):
        if spam not in cache:
            cache[spam] = func(eggs, beans, spam, cheese)
        return cache[spam]
    return wrapper

But there are a lot more generic decorators than specific ones.

Upvotes: 10

Related Questions