Python Tricks: How to Write “Debuggable” Decorators
When you use a decorator, really what you’re doing is replacing one funciton with another. One downside of this process is that it “hides” some of the metadata attached to the original(undecorated) function.
For example, the original function name, its doctoring, and parameter list are hidden by the wrapper closure:
In [4]: def uppercase(func):
...: def wrapper():
...: original_result = func()
...: modified_result = original_result.upper()
...: return modified_result
...: return wrapper
In [1]: def greet():
...: """Return a friendly greeting."""
...: return 'Hello!'
In [2]: decorated_greet = uppercase(greet)
If you try to access any of that function metadata, you’ll see the wrapper closure’s metadata instead:
In [6]: greet.__name__
Out[6]: 'greet'
In [7]: greet.__doc__
Out[7]: 'Return a friendly greeting.'
In [8]: decorated_greet.__name__
Out[8]: 'wrapper'
In [9]: decorated_greet.__doc__
None
This makes debugging and working with the Python interpreter awkward and challenging. Thankfully there’s a quick fix for this: the functools.wraps decorator included in Python’s standard library.
You can use functors.wrapsin your own decorators to copy over the lost metadata from the undecorated function to the decorator closure.
Here’s an example:
import functools
def uppercase(func):
@functools.wraps(func)
def wrapper():
return func().upper()
return wrapper
Applying functools.wraps to the wrapper closure returned by the decorator carries over the doctoring and other metadata of the input function:
greet.__name__
'greet'
greet.__doc__
'Return a friendly greeting.'
As a best practice, I’d recommended that you use functools.wraps in all of the decorators you write yourself. It doesn’t take much time and it will save you (and others) debugging headaches down the road.
Oh, and congratulations–you’ve made it all the way to the end of this complicated chapter and learned a whole lot about decorators in Python. Great job!