Day 15: Decorators
What Is a Decorator?
A pattern that wraps a function to insert additional behavior before and after execution.
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f"{func.__name__} execution time: {elapsed:.4f}s")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
return "done"
slow_function() # slow_function execution time: 1.00xxs
functools.wraps
Preserves the metadata of the original function.
from functools import wraps
def log_call(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Called: {func.__name__}({args}, {kwargs})")
return func(*args, **kwargs)
return wrapper
@log_call
def add(a, b):
"""Adds two numbers."""
return a + b
print(add.__name__) # add (would be wrapper without wraps)
print(add.__doc__) # Adds two numbers.
Decorators with Parameters
def repeat(n):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
# Hello, Alice! (printed 3 times)
Practical Decorator: Cache
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(50)) # 12586269025 (computed quickly)
Today’s Exercises
- Create a
count_callsdecorator that counts how many times a function is called. - Create a
validate_positivedecorator that raises an error if the return value is negative. - Create a
retry(max_attempts)decorator that automatically retries function execution.