Python第十二节 装饰器使用详解及注意事项
前言
装饰器是Python
中一种强大的语法特性,它允许在不修改原函数代码的情况下,为函数添加新的功能。装饰器本质上是一个高阶函数,它接受一个函数作为参数,并返回一个新的函数。
应用场景
日志记录
: 装饰器可用于记录函数的调用信息、参数和返回值。
性能分析
: 可以使用装饰器来测量函数的执行时间。
权限控制
: 装饰器可用于限制对某些函数的访问权限。
缓存
: 装饰器可用于实现函数结果的缓存,以提高性能。
内置装饰器
@staticmethod
: 将方法定义为静态方法,不需要实例化类即可调用。
@classmethod
: 将方法定义为类方法,第一个参数是类本身(通常命名为 cls)。
@property
: 将方法转换为属性,使其可以像属性一样访问。
1. 基本语法
def decorator(func):def wrapper(*args, **kwargs):# 在调用原函数前的操作result = func(*args, **kwargs)# 在调用原函数后的操作return resultreturn wrapper@decoratordef my_function():pass
2. 装饰器案例分析
2.1 计时装饰器
import timeimport functoolsdef timer(func):"""计算函数执行时间的装饰器"""@functools.wraps(func)def wrapper(*args, **kwargs):start_time = time.time()result = func(*args, **kwargs)end_time = time.time()print(f"函数 {func.__name__} 执行耗时: {end_time - start_time:.4f}秒")return resultreturn wrapper@timerdef fibonacci(n):"""计算斐波那契数列"""if n <= 1:return nreturn fibonacci(n-1) + fibonacci(n-2)# 使用print(fibonacci(10)) # 55
2.2 日志记录装饰器
import functoolsfrom datetime import datetimedef logger(func):"""记录函数调用信息的装饰器"""@functools.wraps(func)def wrapper(*args, **kwargs):call_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")print(f"[{call_time}] 调用函数: {func.__name__}")print(f" 参数: args={args}, kwargs={kwargs}")try:result = func(*args, **kwargs)print(f" 返回值: {result}")return resultexcept Exception as e:print(f" 异常: {e}")raisereturn wrapper@loggerdef divide(a, b):return a / b# 使用divide(10, 2)divide(10, 0) # 会记录异常
2.3 参数化装饰器
import functoolsdef repeat(num_times):"""重复执行函数的装饰器"""def decorator_repeat(func):@functools.wraps(func)def wrapper(*args, **kwargs):for _ in range(num_times):result = func(*args, **kwargs)return resultreturn wrapperreturn decorator_repeat@repeat(num_times=3)def greet(name):print(f"Hello, {name}!")# 使用greet("Andy") # 打印 3次 # Hello Andy# Hello Andy# Hello Andy
2.4 类装饰器
类装饰器可以用于:
添加/修改类的方法或属性
拦截实例化过程
实现单例模式、日志记录、权限检查等功能
类装饰器有两种常见形式:
函数形式的类装饰器(接收类作为参数,返回新类)
类形式的类装饰器(实现 __call__
方法,使其可调用)
class CountCalls:"""统计函数调用次数的类装饰器"""def __init__(self, func):functools.update_wrapper(self, func)self.func = funcself.num_calls = 0def __call__(self, *args, **kwargs):self.num_calls += 1print(f"调用 {self.func.__name__} 第 {self.num_calls} 次")return self.func(*args, **kwargs)@CountCallsdef say_hello():print("Hello!")# 使用say_hello()say_hello()print(f"总调用次数: {say_hello.num_calls}")
2.5 权限验证装饰器
import functoolsdef require_role(required_role):"""基于角色的权限验证装饰器"""def decorator(func):@functools.wraps(func)def wrapper(user, *args, **kwargs):if user.get('role') != required_role:raise PermissionError(f"需要 {required_role} 权限,当前用户权限: {user.get('role')}")return func(user, *args, **kwargs)return wrapperreturn decorator# 用户数据users = [{'name': 'Alice', 'role': 'admin'},{'name': 'Bob', 'role': 'user'}]@require_role('admin')def delete_user(current_user, username):print(f"{current_user['name']} 删除了用户 {username}")# 使用try:delete_user(users[0], "Charlie") # 成功delete_user(users[1], "Charlie") # 失败except PermissionError as e:print(f"权限错误: {e}")
2.6 缓存装饰器
import functoolsfrom typing import Anydef cache(func):"""简单的缓存装饰器"""cached_results = {}@functools.wraps(func)def wrapper(*args, **kwargs):# 创建缓存键key = str(args) + str(sorted(kwargs.items()))if key in cached_results:print(f"从缓存中获取 {func.__name__}{args} 的结果")return cached_results[key]result = func(*args, **kwargs)cached_results[key] = resultprint(f"计算 {func.__name__}{args} 的结果并缓存")return resultreturn wrapper@cachedef expensive_operation(x, y):time.sleep(1) # 模拟耗时操作return x * y# 使用print(expensive_operation(3, 4)) # 第一次计算print(expensive_operation(3, 4)) # 从缓存获取
3. 多个装饰器的使用
def decorator1(func):@functools.wraps(func)def wrapper(*args, **kwargs):print("装饰器1 - 前")result = func(*args, **kwargs)print("装饰器1 - 后")return resultreturn wrapperdef decorator2(func):@functools.wraps(func)def wrapper(*args, **kwargs):print("装饰器2 - 前")result = func(*args, **kwargs)print("装饰器2 - 后")return resultreturn wrapper@decorator1@decorator2def example():print("原始函数")# 使用example()# 输出:# 装饰器1 - 前# 装饰器2 - 前# 原始函数# 装饰器2 - 后# 装饰器1 - 后
4. 使用注意事项
4.1 保留元信息
错误做法:
def bad_decorator(func):def wrapper(*args, **kwargs):return func(*args, **kwargs)return wrapper@bad_decorator
def my_function():"""这是一个测试函数"""passprint(my_function.__name__) # 输出: wrapper
print(my_function.__doc__) # 输出: None
正确做法:
import functoolsdef good_decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):return func(*args, **kwargs)return wrapper@good_decoratordef my_function():"""这是一个测试函数"""passprint(my_function.__name__) # 输出: my_functionprint(my_function.__doc__) # 输出: 这是一个测试函数
4.2 处理带参数的装饰器
import functoolsdef smart_decorator(condition=True):def decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):if condition:print("装饰器功能启用")else:print("装饰器功能禁用")return func(*args, **kwargs)return wrapperreturn decorator# 使用
@smart_decorator(condition=True)
def function1():print("函数1执行")@smart_decorator(condition=False)
def function2():print("函数2执行")
4.3 避免装饰器副作用
def safe_decorator(func):"""安全的装饰器,处理异常"""@functools.wraps(func)def wrapper(*args, **kwargs):try:return func(*args, **kwargs)except Exception as e:print(f"函数 {func.__name__} 执行出错: {e}")# 可以选择返回默认值或重新抛出异常return Nonereturn wrapper@safe_decorator
def risky_operation():return 1 / 0 # 这会引发异常result = risky_operation() # 不会崩溃,会打印错误信息
5. 实际应用场景
5.1 Web
框架中的路由装饰器
class Router:def __init__(self):self.routes = {}def route(self, path):def decorator(func):self.routes[path] = funcreturn funcreturn decoratorrouter = Router()@router.route("/home")def home():return "Home Page"@router.route("/about")def about():return "About Page"# 使用print(router.routes) # 输出: {'/home': <function home>, '/about': <function about>}
5.2 数据库事务装饰器
def transaction(db_connection):def decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):try:result = func(*args, **kwargs)db_connection.commit()print("事务提交成功")return resultexcept Exception as e:db_connection.rollback()print(f"事务回滚: {e}")raisereturn wrapperreturn decorator
总结
装饰器是用途:
增强函数功能:添加日志、计时、缓存等功能
代码复用:将通用功能抽象为装饰器
保持代码整洁:避免修改原函数代码
实现AOP:面向切面编程
最佳实践:
- 始终使用
@functools.wraps
保留函数元信息 - 为装饰器编写清晰的文档字符串
- 考虑装饰器的性能和副作用
- 在团队中建立装饰器使用规范
通过合理使用装饰器,可以大幅提高代码的可维护性和可读性。