Real-World Decorator Examples
Logging Decorator
A common use case for decorators is logging function calls. This can be useful for debugging, monitoring, or auditing purposes.
def log_function_call(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args={args} and kwargs={kwargs}")
return func(*args, **kwargs)
return wrapper
@log_function_call
def add_numbers(a, b):
return a + b
result = add_numbers(2, 3)
print(result)
This will output:
Calling add_numbers with args=(2, 3) and kwargs={}
5
Caching Decorator
Decorators can also be used to cache the results of expensive function calls, improving performance.
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n <= 1:
return n
else:
return (fibonacci(n-1) + fibonacci(n-2))
print(fibonacci(100))
The lru_cache
decorator from the functools
module provides a simple way to implement a Least Recently Used (LRU) cache for function results.
Access Control Decorator
Decorators can be used to enforce access control to functions or methods, ensuring that only authorized users can access certain functionality.
from functools import wraps
def require_admin(func):
@wraps(func)
def wrapper(*args, **kwargs):
if not is_admin(args[0]):
raise ValueError("Access denied. You must be an admin.")
return func(*args, **kwargs)
return wrapper
class User:
def __init__(self, name, is_admin):
self.name = name
self.is_admin = is_admin
@require_admin
def delete_user(self, user_to_delete):
print(f"Deleting user: {user_to_delete.name}")
admin = User("LabEx", True)
regular_user = User("John", False)
admin.delete_user(regular_user) ## Works
regular_user.delete_user(admin) ## Raises ValueError
In this example, the require_admin
decorator checks if the user calling the delete_user
method is an admin before allowing the operation to proceed.
Retry Decorator
Decorators can also be used to implement a retry mechanism for functions that may fail due to temporary issues, such as network errors or API rate limits.
import time
from functools import wraps
def retry(max_retries=3, delay=1):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
retries = 0
while retries < max_retries:
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Function {func.__name__} failed. Retrying... ({retries+1}/{max_retries})")
retries += 1
time.sleep(delay)
raise Exception(f"Maximum number of retries ({max_retries}) reached for function {func.__name__}")
return wrapper
return decorator
@retry(max_retries=3, delay=2)
def flaky_function():
## Simulate a flaky function that fails 50% of the time
if random.random() < 0.5:
raise Exception("Oops, something went wrong!")
return "Success!"
print(flaky_function())
In this example, the retry
decorator will automatically retry the flaky_function
up to 3 times, with a 2-second delay between each attempt, before raising an exception.
These are just a few examples of the many real-world use cases for decorators in Python. Decorators are a powerful and flexible tool that can help you write more modular, maintainable, and reusable code.