1. Simplified Explanation of Core Principles
The essence of a decorator is to “add extra functionality to a function without modifying its original code”. It can be understood as giving a function an “outer garment”:
- Original Function: like a person (core functionality)
- Decorator: like an outer garment (new features, such as warmth, waterproofing)
- After using the decorator: the person wears the outer garment, retaining their original abilities while adding the characteristics of the garment
2. Basic Implementation Process
1. Define the Decorator (Create the Outer Garment)
def timer(func): # Accepts the original function (like a person running)
def wrapper(): # Outer garment: wraps the original function
start = time.time()
func() # Executes the original function (running)
end = time.time()
print(f"Elapsed time: {end - start} seconds") # New feature (timing)
return wrapper # Returns the person wearing the outer garment
2. Use the Decorator (Put on the Outer Garment)
@timer # Equivalent to run = timer(run)
def run(): # Original function: the person running
print("Running...")
time.sleep(2)
run() # Output: Running... → Elapsed time: 2.0002 seconds
3. Underlying Logic Diagram
Call run() → Actually executes the wrapper function returned by timer
↓
Internal process of wrapper:
1. Record start time (start)
2. Call original function run() → Output "Running..."
3. Record end time (end)
4. Print elapsed time
3. Basic Example Analysis
Example 1: Authentication Required Decorator
def login_required(func):
def wrapper(user):
if user.is_authenticated:
return func(user) # Execute the original function
else:
raise Exception("Please log in first!")
return wrapper
@login_required
def view_profile(user):
print(f"Displaying user profile: {user.name}")
Chinese Principle:
- Function: Check if the user is logged in, prevent operation if not logged in
- Metaphor: Like needing a card to enter a VIP room, the decorator is the card reader at the entrance
- The original function
<span>view_profile</span>
is the service inside the room <span>@login_required </span><span>automatically adds a card reader at the entrance</span>
- When the user calls
<span>view_profile(user)</span>
, they first swipe the card (validate login), then enter the room
Example 2: Decorator with Parameters (Repeat Execution)
def repeat(times):
def decorator(func): # Accepts decorator parameters (times)
def wrapper():
for _ in range(times):
func() # Repeatedly execute the original function
return wrapper
return decorator
@repeat(times=3)
def greet():
print("Hello!")
Chinese Principle:
- Function: Make the function execute a specified number of times
- Metaphor: Like the “repeat” button on a tape recorder
<span>@repeat(times=3) </span><span>sets the repeat count to 3</span>
- When calling
<span>greet()</span>
, the tape recorder automatically plays “Hello!” 3 times
Example 3: Class Decorator (Count Call Times)
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args):
self.count += 1
print(f"Call number {self.count}")
return self.func(*args)
@CountCalls
def process_data():
print("Processing data...")
Chinese Principle:
- Function: Record the number of times the function is called
- Metaphor: Like a customer counter in a supermarket
<span>@CountCalls </span><span>installs a counter at the entrance</span>
- Every time someone enters (calls
<span>process_data()</span>
), the counter automatically increments by 1 and displays
4. Advanced Example Analysis
- Performance Monitoring (@timer)
import time
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.perf_counter()-start:.4f}s")
return result
return wrapper
@timer
def data_processing(n):
return sum(i**2 for i in range(n))
Principle: Like equipping a function with a stopwatch, automatically recording the start and end time during execution. The decorator wraps the timing logic around the original function, and when calling<span>data_processing(1000)</span><code><span>, it will start the timer before executing the core calculation</span>
2. Automatic Caching (@cache)
from functools import lru_cache
@lru_cache(maxsize=1024)
def fibonacci(n):
return n if n < 2 else fibonacci(n-1) + fibonacci(n-2)
Principle: Similar to the “memory function” of a calculator, storing the most recent calculation results in memory. When calling<span>fibonacci(10)</span>
again, it directly returns the cached result, avoiding repeated recursive calculations
3. Authentication Verification (@requires_auth)
def requires_auth(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if not user.is_authenticated:
raise PermissionError("Login required")
return func(user, *args, **kwargs)
return wrapper
@requires_auth
def view_profile(user):
print(f"Welcome {user.name}")
Principle: Similar to an access control system, checking user permissions before each call to<span>view_profile(user)</span>
. The decorator inserts a verification layer before the core business logic, preventing execution if not passed
4. Distributed Lock (@distributed_lock)
def distributed_lock(key_func, timeout=30):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
lock_key = key_func(*args, **kwargs)
with redis.lock(lock_key, timeout=timeout):
return func(*args, **kwargs)
return wrapper
return decorator
@distributed_lock(lambda item_id: f"lock:{item_id}")
def deduct_inventory(item_id):
# Inventory deduction logic
Principle: Similar to a conference room reservation system, ensuring that only one request can modify inventory at the same time through Redis locks. The decorator locks the resource during function execution, preventing concurrent conflicts
5. Code Quality Assurance – Type Validation (@validate_types)
import inspect
def validate_types(func):
sig = inspect.signature(func)
@wraps(func)
def wrapper(*args, **kwargs):
params = sig.bind(*args, **kwargs)
for name, value in params.arguments.items():
if not isinstance(value, sig.parameters[name].annotation):
raise TypeError(f"Invalid type for {name}")
return func(*args, **kwargs)
return wrapper
@validate_types
def process(data: dict[str, int]) -> list[float]:
return [float(v) for v in data.values()]
Principle: Like a quality inspector, checking types before data enters the processing pipeline. The decorator uses reflection to obtain the function parameter type annotations and dynamically validates input legality at runtime
6. Code Quality Assurance – Intelligent Retry (@retry)
def retry(max_attempts=3, delays=(1, 3, 5)):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt, delay in enumerate(delays, 1):
try:
return func(*args, **kwargs)
except Exception:
if attempt == max_attempts: raise
time.sleep(delay)
return None
return wrapper
return decorator
@retry(max_attempts=5)
def call_external_api():
# API call logic
Principle: Similar to a courier attempting to deliver a package multiple times, automatically retrying in case of network fluctuations. The decorator captures exceptions and waits according to the strategy to retry, improving system fault tolerance
7. Route Registration (@route)
routes = {}
def route(path):
def decorator(func):
routes[path] = func
return func
return decorator
@route("/home")
def home():
return "Home Page"
@route("/about")
def about():
return "About Page"
Principle: Similar to a telephone switchboard system, automatically binding URL paths to handling functions. The decorator collects routing information during module loading, achieving request dispatching
8. Asynchronous Coroutine Support (@async_retry)
def async_retry(max_attempts=3):
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
for _ in range(max_attempts):
try:
return await func(*args, **kwargs)
except Exception:
await asyncio.sleep(1)
raise
return wrapper
return decorator
@async_retry(max_attempts=5)
async def fetch_data(url):
# Async data fetching
Principle: An asynchronous version of the automatic retry mechanism, adapting coroutine functions through<span>async/await</span>
syntax. The decorator automatically retries when asynchronous IO operations fail
Practical Suggestions
- Orthogonal Functionality: Each decorator should only do one thing (e.g., logging and caching should be implemented separately)
- Metadata Preservation: Use
<span>@wraps(func)</span><span> to maintain the original function's name and documentation</span>
- Performance Optimization: Avoid multi-layer decorator nesting in high-frequency calling scenarios
- Type Hinting Friendly: Combine with Python 3.5+ type annotation system
- Error Handling: Properly handle exceptions and resource release in decorators
By flexibly combining these patterns, developers can build highly maintainable system architectures like assembling LEGO blocks. For example, an e-commerce system may simultaneously use: authentication verification + distributed locks + performance monitoring + caching acceleration in a four-layer decorator combination.