Python 装饰器完全指南:从入门到实战
Python 装饰器完全指南:从入门到实战
引言
在 Python 编程中,装饰器(Decorator)是最强大也最优雅的特性之一。它允许我们在不修改原函数代码的前提下,动态地为函数添加功能。无论是 Web 框架中的路由定义、API 接口的权限验证,还是性能监控和日志记录,装饰器都在幕后发挥着关键作用。
本文将深入讲解 Python 装饰器的核心概念、工作原理和实际应用场景,帮助你真正掌握这一利器。
一、装饰器的基本概念
什么是装饰器?
装饰器本质上是一个高阶函数,它接受一个函数作为参数,并返回一个新的函数。使用装饰器可以在不改变原函数定义的情况下,为其添加额外的功能。
装饰器的语法使用 INLINE_CODE_0 符号,放在函数定义的上方:
@decorator_name
def my_function():
pass
这等价于:
def my_function():
pass
my_function = decorator_name(my_function)
为什么需要装饰器?
想象一下,你有 100 个函数都需要记录执行时间。如果没有装饰器,你需要在每个函数内部添加计时代码。使用装饰器,你只需定义一次,然后应用到所有需要的函数上。这体现了 Python 的 DRY(Don't Repeat Yourself)原则。
二、基础装饰器示例
示例 1:简单的日志装饰器
import functools
from datetime import datetime
def log_execution(func):
"""记录函数执行的装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"[{datetime.now()}] 开始执行:{func.__name__}")
result = func(*args, **kwargs)
print(f"[{datetime.now()}] 执行完成:{func.__name__}")
return result
return wrapper
@log_execution
def greet(name):
"""问候函数"""
print(f"你好,{name}!")
return f"Hello, {name}!"
# 使用
greet("张三")
输出:
[2026-04-26 11:00:00.123456] 开始执行:greet
你好,张三!
[2026-04-26 11:00:00.123789] 执行完成:greet
示例 2:性能监控装饰器
import time
import functools
def timing_decorator(func):
"""计算函数执行时间的装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
elapsed = end_time - start_time
print(f"{func.__name__} 执行耗时:{elapsed:.6f} 秒")
return result
return wrapper
@timing_decorator
def slow_operation():
"""模拟耗时操作"""
time.sleep(0.5)
return "完成"
slow_operation()
三、带参数的装饰器
有时候我们需要给装饰器本身传递参数,这时需要三层嵌套函数。
示例 3:可配置的重试装饰器
import functools
import time
def retry(max_attempts=3, delay=1):
"""
重试装饰器
:param max_attempts: 最大重试次数
:param delay: 重试间隔(秒)
"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(max_attempts):
try:
print(f"尝试第 {attempt + 1} 次执行 {func.__name__}")
return func(*args, **kwargs)
except Exception as e:
last_exception = e
print(f"第 {attempt + 1} 次失败:{e}")
if attempt < max_attempts - 1:
time.sleep(delay)
raise last_exception
return wrapper
return decorator
@retry(max_attempts=3, delay=0.5)
def unstable_api():
"""模拟不稳定的 API 调用"""
import random
if random.random() < 0.7:
raise ConnectionError("网络连接失败")
return "API 调用成功"
# 使用
try:
result = unstable_api()
print(result)
except Exception as e:
print(f"最终失败:{e}")
示例 4:权限验证装饰器
from functools import wraps
def require_role(required_role):
"""
角色权限验证装饰器
:param required_role: 需要的角色级别
"""
def decorator(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if user.get('role') != required_role:
raise PermissionError(
f"权限不足:需要 {required_role} 角色,"
f"当前为 {user.get('role')}"
)
print(f"用户 {user.get('name')} 通过 {required_role} 验证")
return func(user, *args, **kwargs)
return wrapper
return decorator
@require_role('admin')
def delete_user(user, target_user_id):
"""删除用户(仅管理员可执行)"""
print(f"管理员 {user['name']} 删除了用户 {target_user_id}")
return True
# 测试
admin_user = {'name': '管理员', 'role': 'admin'}
normal_user = {'name': '普通用户', 'role': 'user'}
delete_user(admin_user, 123) # 成功
# delete_user(normal_user, 123) # 抛出 PermissionError
四、类装饰器
装饰器不仅可以是函数,也可以是类。类装饰器通过实现 INLINE_CODE_1 方法来工作。
示例 5:类实现的计数装饰器
import functools
class CountCalls:
"""统计函数调用次数的类装饰器"""
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.func.__name__} 已被调用 {self.count} 次")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello()
say_hello()
say_hello()
输出:
say_hello 已被调用 1 次
Hello!
say_hello 已被调用 2 次
Hello!
say_hello 已被调用 3 次
Hello!
五、实际应用场景
场景 1:Web 框架中的路由装饰器
Flask 等 Web 框架广泛使用装饰器定义路由:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '首页'
@app.route('/user/<username>')
def user_profile(username):
return f'用户:{username}'
@app.route('/api/data', methods=['POST'])
def api_data():
return {'status': 'success'}
场景 2:API 速率限制
import time
from collections import defaultdict
from functools import wraps
def rate_limit(max_calls, period):
"""
API 速率限制装饰器
:param max_calls: 允许的最大调用次数
:param period: 时间窗口(秒)
"""
calls = defaultdict(list)
def decorator(func):
@wraps(func)
def wrapper(client_id, *args, **kwargs):
now = time.time()
# 清理过期记录
calls[client_id] = [
t for t in calls[client_id]
if now - t < period
]
if len(calls[client_id]) >= max_calls:
raise Exception("请求频率超限")
calls[client_id].append(now)
return func(client_id, *args, **kwargs)
return wrapper
return decorator
@rate_limit(max_calls=5, period=60)
def api_request(client_id, data):
return f"处理 {client_id} 的请求:{data}"
场景 3:缓存装饰器(记忆化)
import functools
def memoize(func):
"""缓存函数结果的装饰器"""
cache = {}
@functools.wraps(func)
def wrapper(*args):
if args not in cache:
print(f"计算 {func.__name__}{args}")
cache[args] = func(*args)
else:
print(f"缓存命中 {func.__name__}{args}")
return cache[args]
return wrapper
@memoize
def fibonacci(n):
"""计算斐波那契数列"""
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
fibonacci(5)
fibonacci(5) # 缓存命中
场景 4:事务管理装饰器
from contextlib import contextmanager
def transaction(func):
"""数据库事务装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("开始事务")
try:
result = func(*args, **kwargs)
print("提交事务")
return result
except Exception as e:
print(f"回滚事务:{e}")
raise
return wrapper
@transaction
def transfer_money(from_account, to_account, amount):
"""转账操作"""
print(f"从 {from_account} 转账 {amount} 到 {to_account}")
return True
六、最佳实践和注意事项
1. 使用 functools.wraps
始终使用 INLINE_CODE_2 保留原函数的元数据(名称、文档字符串等):
# 错误做法
def bad_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
# 正确做法
def good_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
2. 装饰器顺序
多个装饰器时,执行顺序是从下往上,从外往内:
@decorator_a
@decorator_b
@decorator_c
def my_func():
pass
# 等价于:decorator_a(decorator_b(decorator_c(my_func)))
3. 保持装饰器通用性
设计装饰器时尽量使其通用,可以应用于多种函数:
# 好的设计:接受任意参数
def universal_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
4. 避免过度使用
装饰器虽然强大,但不要滥用。如果逻辑过于复杂,考虑使用其他设计模式。
七、综合实战:构建一个完整的装饰器库
import time
import logging
import functools
from typing import Callable, Any
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class DecoratorLibrary:
"""装饰器工具库"""
@staticmethod
def log_args(func: Callable) -> Callable:
"""记录函数参数的装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
logger.info(f"{func.__name__} 参数:args={args}, kwargs={kwargs}")
return func(*args, **kwargs)
return wrapper
@staticmethod
def cache_result(expire_seconds: int = 300):
"""带过期时间的缓存装饰器"""
cache = {}
timestamps = {}
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args) -> Any:
now = time.time()
key = str(args)
# 检查缓存是否过期
if key in cache:
if now - timestamps[key] < expire_seconds:
logger.info(f"缓存命中:{func.__name__}")
return cache[key]
else:
del cache[key]
del timestamps[key]
# 计算并缓存
logger.info(f"计算:{func.__name__}")
result = func(*args)
cache[key] = result
timestamps[key] = now
return result
return wrapper
return decorator
@staticmethod
def validate_types(**expected_types):
"""参数类型验证装饰器"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs):
import inspect
sig = inspect.signature(func)
bound = sig.bind(*args, **kwargs)
bound.apply_defaults()
for param_name, expected_type in expected_types.items():
if param_name in bound.arguments:
value = bound.arguments[param_name]
if not isinstance(value, expected_type):
raise TypeError(
f"{param_name} 应该是 {expected_type.__name__}, "
f"实际是 {type(value).__name__}"
)
return func(*args, **kwargs)
return wrapper
return decorator
# 使用示例
@DecoratorLibrary.log_args
@DecoratorLibrary.validate_types(name=str, age=int)
@DecoratorLibrary.cache_result(expire_seconds=60)
def create_user(name: str, age: int) -> dict:
"""创建用户"""
time.sleep(0.1) # 模拟耗时操作
return {"name": name, "age": age, "id": hash(name) % 10000}
# 测试
user1 = create_user("张三", 25)
user2 = create_user("张三", 25) # 缓存命中
print(user1)
结语
装饰器是 Python 中最优雅的特性之一,它让我们能够以声明式的方式为函数添加功能,大大提高了代码的可复用性和可维护性。
通过本文的学习,你应该已经掌握了:
- 装饰器的基本概念和工作原理
- 如何编写基础装饰器和带参数的装饰器
- 类装饰器的实现方式
- 装饰器在实际项目中的多种应用场景
- 编写装饰器的最佳实践
记住,装饰器虽然强大,但也要适度使用。当装饰器逻辑过于复杂时,考虑是否应该重构代码结构。好的装饰器应该是简洁、通用且易于理解的。
现在,开始在你的项目中使用装饰器吧,让代码更加优雅和高效!