Get startedGet started for free

Decorators and metadata

1. Decorators and metadata

One of the problems with decorators is that they obscure the decorated function's metadata. In this lesson, I'll show you why it's a problem and how to fix it.

2. Function with a docstring

Here we have a nice function, sleep_n_seconds(), with a docstring that explains exactly what it does. If we look at the docstring attribute, we can see the text of the docstring.

3. Other metadata

We can also access other metadata for the function, like its name and default arguments.

4. A decorated function

But watch what happens when we decorate sleep_n_seconds() with the timer() decorator as we've done here. When we try to print the docstring, we get nothing back. Even stranger, when we try to look up the function's name, Python tells us that sleep_n_seconds()'s name is "wrapper".

5. The timer decorator

To understand why we have to examine the timer() decorator. Remember that when we write decorators, we almost always define a nested function to return. Because the decorator overwrites the sleep_n_seconds() function, when you ask for sleep_n_seconds()'s docstring or name, you are actually referencing the nested function that was returned by the decorator. In this case, the nested function was called wrapper() and it didn't have a docstring.

6. functools.wraps()

Fortunately, Python provides us with an easy way to fix this. The wraps() function from the functools module is a decorator that you use when defining a decorator. If you use it to decorate the wrapper function that your decorator returns, it will modify wrapper()'s metadata to look like the function you are decorating. Notice that the wraps() decorator takes the function you are decorating as an argument. We haven't talked about decorators that take arguments yet, but we will cover that in the next lesson.

7. The metadata we want

If we use this updated version of the timer() decorator to decorate sleep_n_seconds() and then try to print sleep_n_seconds()'s docstring, we get the result we expect.

8. The metadata we want

Likewise, printing the name or any other metadata now gives you the metadata from the function being decorated rather than the metadata of the wrapper() function.

9. Access to the original function

As an added bonus, using wraps() when creating your decorator also gives you easy access to the original undecorated function via the __wrapped__ attribute. Of course, you always had access to this function via the closure, but this is an easy way to get to it if you need it.

10. Let's practice!

Now that you know how to preserve the metadata of the functions you are decorating take a moment to practice this new skill.