What Does Functools.Wraps Do

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

@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'

decorator module vs functools.wraps

One of the main differences is listed right in the documentation you linked to: decorator preserves the signature of the wrapped function, while wraps does not.

Try each function of a class with functools.wraps decorator

As an alternative to Stefan's answer, the following simply uses @trier without any parameters to decorate functions, and then when printing out the error message we can get the name with func.__name__.

from functools import wraps

def trier(func):
"""Decorator for trying A-class methods"""
@wraps(func)
def inner_func(self, *args, **kwargs):

try:
return func(self, *args, **kwargs)

except:
print(f"An error apeared in {func.__name__}")

return inner_func

class A:
def __init__(self):
self._animals = 2
self._humans = 5

@trier
def animals(self, num):
return self._animals + num

@trier
def humans(self):
return self._humans

print(A().animals(1))

I also fixed a couple of bugs in the code: In trier's try and except the result of calling func was never returned, and you need to include **kwargs in addition to *args so you can use named parameters. I.e. A().animals(num=1) only works when you handle kwargs.

what is the difference between functools.wraps and update_wrapper

functools.wraps is equivalent to:

def wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES):
def decorator(wrapper):
return update_wrapper(wrapper, wrapped=wrapped, ...)
return decorator

It's actually implemented using partial instead of an inner function, but the effect is the same.

The purpose is to allow using it as a decorator:

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

is equivalent to:

def g():
...
g = update_wrapper(g, f)

functools.wraps equivalent for class decorator

No, there isn't, assuming your decorator really subclasses the wrapped class like some_class_decorator does. The output of help is defined by the pydoc.Helper class, which for classes ultimately calls pydoc.text.docclass, which contains this code:

# List the mro, if non-trivial.
mro = deque(inspect.getmro(object))
if len(mro) > 2:
push("Method resolution order:")
for base in mro:
push(' ' + makename(base))
push('')

You can see that it is hard-coded to display the class's real MRO. This is as it should be. The MRO displayed in your last example is not "broken", it is correct. By making your wrapper class inherit from the wrapped class, you added an additional class to the inheritance hierarchy. It would be misleading to show an MRO that left that out, because there really is a class there. In your example, this wrapper class doesn't provide any behavior of its own, but a realistic wrapper class would (or else why would you be doing the wrapping at all?), and you would want to know which behavior came from the wrapper class and which from the wrapped class.

If you wanted, you could make a decorator that dynamically renamed the wrapper class with some name derived from the original, so the MRO would show something like DecoratorWrapper_of_MainClass in the appropriate position. Whether this would be more readable than just having Wrapper there is debatable.

understanding functools.wraps

authenticate is a decorator -- it takes a function and returns a modified version of that function (which is usually implemented by wrapping the function and wrapping it).

Now, the problem with wrappers is that they often don't act exactly like the original function in some ways -- they may be missing docstrings, have the wrong __name__ (wrapper instead of what it should be called), and other blemishes. This might be important if some other code is using that extra information. functools.wraps is a simple function that adds this information from the original function (here, func) to the wrapper function, so it behaves more like the original function. (Technically, it is itself a decorator, which is the confusing part, but you don't have to worry about that detail. Just know that it is a nice tool that copies attributes from a wrapped function to a wrapper function).

Thus, when you write

new_function = authenticate(old_function)

or more commonly

@authenticate
def function(...)

new_function will look more like old_function.



Related Topics



Leave a reply



Submit