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.
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.
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.
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.
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 ** .
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With