In a comment on this answer to another question, someone said that they weren't sure what functools.wraps
was doing. So, I'm asking this question so that there will be a record of it on StackOverflow for future reference: what does functools.wraps
do, exactly?
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.
The functools module, part of Python's standard Library, provides useful features that make it easier to work with high order functions (a function that returns a function or takes another function as an argument ).
__wrapped__ in Python decorators As we can see from the code of the functools module 1, when decorating an object, there is an attribute named __wrapped__ that holds the reference to the original one. So now if we use this, we can access it directly without having to resort to the old quirks.
You can create partial functions in python by using the partial function from the functools library. Partial functions allow one to derive a function with x parameters to a function with fewer parameters and fixed values set for the more limited function.
When you use a decorator, you're replacing one function with another. In other words, if you have a decorator
def logged(func): def with_logging(*args, **kwargs): print(func.__name__ + " was called") return func(*args, **kwargs) return with_logging
then when you say
@logged def f(x): """does some math""" return x + x * x
it's exactly the same as saying
def f(x): """does some math""" return x + x * x f = logged(f)
and your function f
is replaced with the function with_logging
. Unfortunately, this means that if you then say
print(f.__name__)
it will print with_logging
because that's the name of your new function. In fact, if you look at the docstring for f
, it will be blank because with_logging
has no docstring, and so the docstring you wrote won't be there anymore. Also, if you look at the pydoc result for that function, it won't be listed as taking one argument x
; instead it'll be listed as taking *args
and **kwargs
because that's what with_logging takes.
If using a decorator always meant losing this information about a function, it would be a serious problem. That's why we have functools.wraps
. This takes a function used in a decorator and adds the functionality of copying over the function name, docstring, arguments list, etc. And since wraps
is itself a decorator, the following code does the correct thing:
from functools import wraps def logged(func): @wraps(func) def with_logging(*args, **kwargs): print(func.__name__ + " was called") return func(*args, **kwargs) return with_logging @logged def f(x): """does some math""" return x + x * x print(f.__name__) # prints 'f' print(f.__doc__) # prints 'does some math'
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