What does functools.wraps do? What does functools.wraps do? python python

What does functools.wraps do?

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

@loggeddef 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 * xf = logged(f)

and your function f is replaced with the function with_logging. Unfortunately, this means that if you then say


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 wrapsdef logged(func):    @wraps(func)    def with_logging(*args, **kwargs):        print(func.__name__ + " was called")        return func(*args, **kwargs)    return with_logging@loggeddef f(x):   """does some math"""   return x + x * xprint(f.__name__)  # prints 'f'print(f.__doc__)   # prints 'does some math'

As of python 3.5+:

@functools.wraps(f)def g():    pass

Is an alias for g = functools.update_wrapper(g, f). It does exactly three things:

  • it copies the __module__, __name__, __qualname__, __doc__, and __annotations__ attributes of f on g. This default list is in WRAPPER_ASSIGNMENTS, you can see it in the functools source.
  • it updates the __dict__ of g with all elements from f.__dict__. (see WRAPPER_UPDATES in the source)
  • it sets a new __wrapped__=f attribute on g

The consequence is that g appears as having the same name, docstring, module name, and signature than f. The only problem is that concerning the signature this is not actually true: it is just that inspect.signature follows wrapper chains by default. You can check it by using inspect.signature(g, follow_wrapped=False) as explained in the doc. This has annoying consequences:

  • the wrapper code will execute even when the provided arguments are invalid.
  • the wrapper code can not easily access an argument using its name, from the received *args, **kwargs. Indeed one would have to handle all cases (positional, keyword, default) and therefore to use something like Signature.bind().

Now there is a bit of confusion between functools.wraps and decorators, because a very frequent use case for developing decorators is to wrap functions. But both are completely independent concepts. If you're interested in understanding the difference, I implemented helper libraries for both: decopatch to write decorators easily, and makefun to provide a signature-preserving replacement for @wraps. Note that makefun relies on the same proven trick than the famous decorator library.

I very often use classes, rather than functions, for my decorators. I was having some trouble with this because an object won't have all the same attributes that are expected of a function. For example, an object won't have the attribute __name__. I had a specific issue with this that was pretty hard to trace where Django was reporting the error "object has no attribute '__name__'". Unfortunately, for class-style decorators, I don't believe that @wrap will do the job. I have instead created a base decorator class like so:

class DecBase(object):    func = None    def __init__(self, func):        self.__func = func    def __getattribute__(self, name):        if name == "func":            return super(DecBase, self).__getattribute__(name)        return self.func.__getattribute__(name)    def __setattr__(self, name, value):        if name == "func":            return super(DecBase, self).__setattr__(name, value)        return self.func.__setattr__(name, value)

This class proxies all the attribute calls over to the function that is being decorated. So, you can now create a simple decorator that checks that 2 arguments are specified like so:

class process_login(DecBase):    def __call__(self, *args):        if len(args) != 2:            raise Exception("You can only specify two arguments")        return self.func(*args)