Python tutorials > Advanced Python Concepts > Decorators > How to use decorators with arguments?
How to use decorators with arguments?
Basic Decorator Structure
my_decorator function takes the function to be decorated as an argument and returns a wrapper function. The wrapper function executes code before and after the original function.
def my_decorator(func):
def wrapper(*args, **kwargs):
# Do something before
result = func(*args, **kwargs)
# Do something after
return result
return wrapper
@my_decorator
def say_hello(name):
return f"Hello, {name}!"
print(say_hello("Alice"))
Creating a Decorator with Arguments
repeat is the outer function that takes num_times as an argument. It returns decorator_repeat, which is the actual decorator. The decorator_repeat function then takes the function to be decorated (func) as an argument and returns the wrapper function.
def repeat(num_times):
def decorator_repeat(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator_repeat
@repeat(num_times=3)
def greet(name):
print(f"Hello, {name}!")
greet("Bob")
Explanation of the Code
repeat function is called with num_times=3, which returns the decorator_repeat function. Then, the decorator_repeat function is applied to the greet function. The wrapper function inside decorator_repeat executes the greet function multiple times based on the num_times argument. This approach allows you to create flexible decorators that can be customized using arguments.
Concepts Behind the Snippet
repeat function returns another function (decorator_repeat), which 'closes over' the num_times argument. This means that decorator_repeat remembers the value of num_times even after repeat has finished executing. The decorator_repeat function then returns another function (wrapper) which closes over the original function func. This pattern enables the creation of decorators that can be parameterized.
Real-Life Use Case Section
Best Practices
functools.wraps: When creating decorators, use functools.wraps to preserve the original function's metadata (name, docstring, etc.). This improves introspection and debugging.
Using functools.wraps
functools.wraps copies the metadata from the decorated function to the wrapper function. Without it, greet.__name__ would be wrapper and greet.__doc__ would be None. This is important for debugging and understanding your code.
import functools
def repeat(num_times):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator_repeat
@repeat(num_times=3)
def greet(name):
"""Greets someone."""
print(f"Hello, {name}!")
print(greet.__name__)
print(greet.__doc__)
Interview Tip
functools.wraps is crucial.
When to use them
Memory Footprint
Alternatives
Pros
Cons
functools.wraps, introspection can be difficult.
FAQ
-
Why use
functools.wraps?
functools.wrapspreserves the original function's metadata (name, docstring, etc.), making debugging and introspection easier. Without it, the decorated function would lose its original identity. -
Can I use decorators with arguments on class methods?
Yes, decorators with arguments can be used on class methods. The first argument passed to the method will beself. -
How do I handle exceptions in decorators?
You can handle exceptions within the wrapper function. You can either catch and log the exception or re-raise it after performing some cleanup or logging.