Decorators
Decorators Introduction
You need to add logging to 50 functions, or timing to every API endpoint, or authentication checks everywhere. Instead of modifying each function, decorators let you wrap functions with reusable behavior - just add @log or @timed above any function definition.
Metadata Problem
simple.py
# Simple decorator
def shout_decorator(func):
def wrapper():
print("BEFORE!")
func()
print("AFTER!")
return wrapper
message =
# Decorate with @
@shout_decorator
def greet():
print(message)
greet()
# Simple decorator
def shout_decorator(func):
def wrapper():
print("BEFORE!")
func()
print("AFTER!")
return wrapper
message =
# Decorate with @
@shout_decorator
def greet():
print(message)
greet()
# Simple decorator
def shout_decorator(func):
def wrapper():
print("BEFORE!")
func()
print("AFTER!")
return wrapper
message =
# Decorate with @
@shout_decorator
def greet():
print(message)
greet()
use_cases.py
# Common use-cases
import time
from functools import wraps
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"calling {func.__name__}({args}, {kwargs})")
return func(*args, **kwargs)
return wrapper
def timing(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
# Apply decorators
@log_calls
def add(a, b):
return a + b
@timing
def slow_task():
time.sleep(0.01)
return "done"
print(add(2, 3))
print(slow_task())
multiple.py
# Multiple decorators
def upper(func):
def wrapper():
result = func()
return result.upper()
return wrapper
def exclaim(func):
def wrapper():
result = func()
return result + "!"
return wrapper
# Bottom to top application
@upper
@exclaim
def greet():
return "hello"
print(greet())
# Equivalent to
def greet2():
return "hello"
greet2 = upper(exclaim(greet2))
print(greet2())
execution_order.py
# Decorator execution order
def trace(func):
print(f"decorating {func.__name__}")
def wrapper():
print(f"calling {func.__name__}")
func()
return wrapper
# Decorator runs at definition time
print("defining function...")
@trace
def hello():
print("hello from function")
print("calling function...")
hello()
metadata.py
# Preserving metadata
from functools import wraps
def without_wraps(func):
def wrapper():
func()
return wrapper
def with_wraps(func):
@wraps(func)
def wrapper():
func()
return wrapper
@without_wraps
def func_a():
"""doc for func_a"""
pass
@with_wraps
def func_b():
"""doc for func_b"""
pass
# Compare metadata
print("without wraps:")
print(" name:", func_a.__name__)
print(" doc:", func_a.__doc__)
print("\nwith wraps:")
print(" name:", func_b.__name__)
print(" doc:", func_b.__doc__)
decorator - a function that takes a function and returns a modified version, applied with @decorator syntax
decorator stacking - applying multiple decorators, executed from bottom to top
Exercise: practical.py
Create a retry decorator that attempts a function multiple times