- Understanding Decorators
- Function Wrapping and Augmentation
- Using functools.wraps
- Decorating Functions with Arguments
- Practical Applications of Decorators
- Summary
- Tasks
Decorators are functions that modify the behavior of other functions or methods. They allow you to wrap another function to extend its behavior without permanently modifying it.
A basic decorator function takes another function as an argument, defines a wrapper function, and returns the wrapper function.
def decorator(func):
def wrapper():
print("Start")
func()
print("End")
return wrapper
@decorator
def ordinary():
print("Ordinary Function")
ordinary()
Output:
Start
Ordinary Function
End
Explanation: The decorator
function wraps the ordinary
function, adding behavior before and after the original function call.
Decorators can be used to augment the behavior of functions, such as adding logging, timing, or access control.
import time
def timer(func):
def wrapper():
t1 = time.time()
print("Start")
func()
print("End")
t2 = time.time()
res = round((t2 - t1) * 1000)
print(f"{func.__name__} executed in {res} milliseconds")
return wrapper
@timer
def task():
time.sleep(3)
task()
Output:
Start
End
task executed in 3000 milliseconds
Explanation: The timer
decorator measures the execution time of the task
function.
The functools.wraps
decorator is used to preserve the original function's metadata when it is wrapped by a decorator.
import functools
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("Before")
res = func(*args, **kwargs)
print("After")
return res
return wrapper
@decorator
def add(x, y):
"""Adds two numbers."""
return x + y
print(add(5, y=5))
print(add.__name__)
print(add.__doc__)
Output:
Before
After
10
add
Adds two numbers.
Explanation: The functools.wraps
decorator ensures that the add
function retains its original name and docstring.
Decorators can also handle functions with arguments by using *args
and **kwargs
in the wrapper function.
def decorator(func):
def wrapper(*args, **kwargs):
print("Before")
res = func(*args, **kwargs)
print("After")
return res
return wrapper
@decorator
def add(x, y):
return x + y
print(add(5, y=5))
Output:
Before
After
10
Explanation: The decorator
function wraps the add
function, allowing it to handle any number of positional and keyword arguments.
Decorators have many practical applications, such as logging, access control, and memoization.
def log(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with {args} and {kwargs}")
return func(*args, **kwargs)
return wrapper
@log
def multiply(x, y):
return x * y
print(multiply(3, 4))
Output:
Calling multiply with (3, 4) and {}
12
Explanation: The log
decorator logs the function call with its arguments.
def requires_auth(func):
def wrapper(*args, **kwargs):
if not kwargs.get('authenticated', False):
print("Authentication required")
return
return func(*args, **kwargs)
return wrapper
@requires_auth
def get_data(*args, **kwargs):
return "Sensitive data"
print(get_data(authenticated=True))
print(get_data(authenticated=False))
Output:
Sensitive data
Authentication required
Explanation: The requires_auth
decorator checks if the user is authenticated before allowing access to the get_data
function.
In this chapter, we covered decorators, including understanding decorators, function wrapping and augmentation, using functools.wraps
, decorating functions with arguments, and practical applications of decorators.
- Write a decorator that logs the execution time of a function.
- Write a decorator that checks if a user is authorized to access a function.
- Write a decorator that caches the results of a function to improve performance.
- Write a decorator that adds a retry mechanism to a function that may fail.