Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 831
Saif I Avatar asked Mar 20 '18 04:03

Saif I


People also ask

Can Python decorators take arguments?

The decorator arguments are accessible to the inner decorator through a closure, exactly like how the wrapped() inner function can access f . And since closures extend to all the levels of inner functions, arg is also accessible from within wrapped() if necessary.

What are the Python decorators?

A decorator is a design pattern in Python that allows a user to add new functionality to an existing object without modifying its structure. Decorators are usually called before the definition of a function you want to decorate.

What does @wraps do in Python?

wraps() is a decorator that is applied to the wrapper function of a decorator. It updates the wrapper function to look like wrapped function by copying attributes such as __name__, __doc__ (the docstring), etc. Parameters: wrapped: The function name that is to be decorated by wrapper function.

What is the use of Kwargs in Python?

kwargs is variable name used for keyword arguments, another variable name can be used. The important part is that it's a dictionary and it's unpacked with the double asterisk operator ** .


1 Answers

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.

like image 67
abarnert Avatar answered Oct 11 '22 05:10

abarnert