Hi Pythonistas!,
Python's decorators are a powerful feature that allows developers to modify the behavior of functions or methods. However, when creating custom decorators, you might encounter a common issue: the metadata of the original function, such as its name and docstring, can be lost. This is where the @wraps decorator from Python's functools module comes to the rescue.
If you are not familiar with decorator please visit our How to write decorator post
In this post, we'll explore how the @wraps decorator works, why it's important, and how to use it effectively in your Python code.
Why Do We Need @wraps?
When you create a decorator in Python, you typically define an inner function (often called wrapper) that adds some extra functionality before or after the original function is called. However, without the @wraps decorator, this wrapper function will replace the original function’s metadata. This can lead to issues, especially when you're relying on attributes like __name__, __doc__, and others for introspection or documentation purposes.
Code
def print_5_times(function):
def inner(*args, **kwargs):
for _ in range(5):
function(*args, **kwargs)
return inner
def print_hello_world_without_decorator():
"""
This is doc string
"""
print("Hello world")
@print_5_times
def print_hello_world_with_decorator():
"""
This is doc string
"""
print("Hello world")
print(print_hello_world_without_decorator.__name__)
print(print_hello_world_without_decorator.__doc__)
print(print_hello_world_with_decorator.__name__)
print(print_hello_world_with_decorator.__doc__)
Output
print_hello_world_without_decorator
This is doc string
inner
None
We can see that when we use decorator __name__ and __doc__ is different because when we use decorator __name__ and __doc__ of the function is replace with the inner function of the decorator
Introducing @wraps: A Simple Solution
To preserve the original function’s metadata, Python provides the @wraps decorator from the functools module. This decorator is applied to the wrapper function inside your custom decorator. Here’s how you can use it:
Code
from functools import wraps
def print_5_times(function):
@wraps(function)
def inner(*args, **kwargs):
for _ in range(5):
function(*args, **kwargs)
return inner
@print_5_times
def print_hello_world_with_decorator():
"""
This is doc string
"""
print("Hello world")
print(print_hello_world_with_decorator.__name__)
print(print_hello_world_with_decorator.__doc__)
Output
print_hello_world_with_decorator
This is doc string
By using @wraps(func), the wrapper function retains the original function’s name, docstring, and other important attributes.
Why Preserving Metadata Matters
Maintaining the original function’s metadata is crucial for several reasons:
Documentation: When generating documentation for your code, having accurate docstrings and function names is essential.
Introspection: Tools and libraries that inspect your code, such as debuggers, profilers, and type checkers, rely on accurate metadata to provide meaningful output.
Testing: When writing tests, you may want to check certain attributes of functions. Having the correct metadata makes this process smoother and more reliable.
Conclusion
The @wraps decorator might seem like a small addition, but it plays a vital role in ensuring that your decorated functions maintain their original identity. Whether you’re writing custom decorators for logging, authentication, or any other purpose, make it a habit to use @wraps.