当前位置:首页 > Python > 正文内容

Python 装饰器高级实战:类装饰器与带参数装饰器完全指南

admin2周前 (03-23)Python20
# Python 装饰器高级实战:类装饰器与带参数装饰器完全指南 ## 简介 Python 装饰器(Decorator)是 Python 中最强大的特性之一,它可以在不修改原始函数代码的情况下,动态地为函数添加新功能。本教程将深入探讨装饰器的高级用法,包括类装饰器和带参数装饰器,并通过原创实例展示如何在实际项目中灵活运用这些技术。 ## 正文

装饰器本质上是一个接受函数作为参数的函数,它返回一个新的函数来替代原始函数。在基础用法中,装饰器常用于日志记录、性能测试、权限验证等场景。然而,当业务逻辑变得更复杂时,简单的函数装饰器可能无法满足需求,这时就需要使用更高级的装饰器形式。

让我们从一个实际需求开始:假设我们正在构建一个 Web API 框架,需要为多个端点添加统一的访问日志和性能监控功能。我们可以首先实现一个基础装饰器:

```python import time from functools import wraps from datetime import datetime def log_execution(func): @wraps(func) def wrapper(*args, **kwargs): start_time = time.time() print(f"[{datetime.now()}] 开始执行: {func.__name__}") result = func(*args, **kwargs) elapsed = time.time() - start_time print(f"[{datetime.now()}] 完成: {func.__name__}, 耗时: {elapsed:.3f}秒") return result return wrapper ```

这个基础装饰器可以很好地工作,但它的灵活性有限。现在让我们进入更高级的主题。

### 类装饰器

类装饰器使用类而非函数来实现装饰逻辑。它通过实现 `__call__` 方法,使得类的实例可以像函数一样被调用。类装饰器的优势在于可以维护装饰器自身的状态,实现更复杂的功能。

考虑一个场景:我们需要跟踪每个函数被调用的次数,并且能够在运行时查询这些统计信息。使用类装饰器可以轻松实现:

```python class CallCounter: def __init__(self, func): self.func = func self.call_count = 0 self.__name__ = func.__name__ # 保留原始函数名 def __call__(self, *args, **kwargs): self.call_count += 1 print(f"函数 {self.func.__name__} 第 {self.call_count} 次被调用") return self.func(*args, **kwargs) def get_stats(self): return { 'function_name': self.func.__name__, 'total_calls': self.call_count } ```

使用这个类装饰器非常直观:

```python @CallCounter def calculate_square(n): return n * n @CallCounter def calculate_cube(n): return n ** 3 # 测试调用 print(calculate_square(5)) # 函数 calculate_square 第 1 次被调用 print(calculate_cube(3)) # 函数 calculate_cube 第 1 次被调用 print(calculate_square(10)) # 函数 calculate_square 第 2 次被调用 # 查询统计信息 print(calculate_square.get_stats()) # {'function_name': 'calculate_square', 'total_calls': 2} print(calculate_cube.get_stats()) # {'function_name': 'calculate_cube', 'total_calls': 1} ```

类装饰器的另一个强大应用场景是实现缓存机制。下面是一个基于 LRU(最近最少使用)策略的缓存装饰器:

```python class LRUCache: def __init__(self, func, max_size=3): self.func = func self.max_size = max_size self.cache = {} # 参数到结果的映射 self.access_order = [] # 记录访问顺序 def __call__(self, *args, **kwargs): # 创建参数的缓存键 cache_key = (args, tuple(sorted(kwargs.items()))) if cache_key in self.cache: # 命中缓存,更新访问顺序 self.access_order.remove(cache_key) self.access_order.append(cache_key) print(f"缓存命中: {args}") return self.cache[cache_key] # 缓存未命中,调用原始函数 result = self.func(*args, **kwargs) # 如果缓存已满,移除最久未使用的项 if len(self.cache) >= self.max_size: oldest_key = self.access_order.pop(0) del self.cache[oldest_key] print(f"缓存淘汰: {oldest_key}") self.cache[cache_key] = result self.access_order.append(cache_key) print(f"缓存存储: {args}") return result def clear_cache(self): self.cache.clear() self.access_order.clear() print("缓存已清空") ```

这个缓存装饰器可以显著提高重复计算的性能:

```python @LRUCache(max_size=3) def expensive_computation(x): print(f"执行复杂计算: {x}") return x * x # 第一次调用,执行计算 print(expensive_computation(5)) # 执行复杂计算: 5 # 第二次调用,从缓存获取 print(expensive_computation(5)) # 缓存命中: (5,) # 调用不同的参数 print(expensive_computation(10)) # 执行复杂计算: 10 print(expensive_computation(15)) # 执行复杂计算: 15 # 缓存已满,再次调用新参数会淘汰最老的 print(expensive_computation(20)) # 执行复杂计算: 20, 缓存淘汰 ``` ### 带参数的装饰器

带参数的装饰器需要三层嵌套结构:装饰器工厂函数、真正的装饰器函数、以及包装函数。这种结构使得装饰器可以接受自定义参数,提供更高的灵活性。

让我们实现一个重试装饰器,它可以在函数执行失败时自动重试指定次数:

```python def retry(max_attempts=3, delay=1, exceptions=(Exception,)): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_attempts): try: return func(*args, **kwargs) except exceptions as e: if attempt == max_attempts - 1: raise # 最后一次尝试失败,抛出异常 print(f"尝试 {attempt + 1}/{max_attempts} 失败: {e}") print(f"等待 {delay} 秒后重试...") time.sleep(delay) return None return wrapper return decorator ```

使用重试装饰器可以让不稳定的网络调用更加健壮:

```python import random @retry(max_attempts=3, delay=1, exceptions=(ValueError, ConnectionError)) def unstable_network_call(url): # 模拟不确定的网络响应 if random.random() < 0.7: raise ConnectionError("网络连接超时") return f"成功获取: {url}" # 测试 try: result = unstable_network_call("https://api.example.com/data") print(f"最终结果: {result}") except Exception as e: print(f"所有尝试失败: {e}") ```

带参数装饰器的另一个实用场景是实现权限检查。例如,我们可以创建一个装饰器来验证用户是否具有特定的权限:

```python def require_permission(*required_permissions): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): # 假设第一个参数是用户对象 user = args[0] if args else kwargs.get('user') if not user: raise ValueError("用户信息缺失") user_permissions = user.get('permissions', []) missing_permissions = set(required_permissions) - set(user_permissions) if missing_permissions: raise PermissionError( f"权限不足。需要: {', '.join(missing_permissions)}" ) print(f"权限验证通过,执行: {func.__name__}") return func(*args, **kwargs) return wrapper return decorator ```

权限装饰器的使用示例:

```python @require_permission('read', 'write') def update_user_data(user, data): print(f"更新用户数据: {data}") return "更新成功" # 测试 admin_user = {'name': 'admin', 'permissions': ['read', 'write', 'delete']} regular_user = {'name': 'user', 'permissions': ['read']} print(update_user_data(admin_user, {'name': '张三', 'age': 25})) # 权限验证通过 # update_user_data(regular_user, {'name': '李四'}) # 抛出 PermissionError ``` ### 组合多个装饰器

在 Python 中,我们可以通过叠加多个装饰器来实现功能的组合。装饰器的执行顺序是从内向外,即最下方的装饰器最先执行。

```python @require_permission('read', 'write') @retry(max_attempts=2) @log_execution def critical_operation(user, data): print(f"执行关键操作: {data}") return "操作完成" ```

在这个例子中,执行顺序是: 1. `log_execution` 最先执行(记录开始时间) 2. `retry` 在其外层处理重试逻辑 3. `require_permission` 在最外层检查权限

这种组合方式让每个装饰器专注于单一职责,通过组合实现复杂的功能需求。

## 最佳实践建议

1. 使用 `functools.wraps` 保留原始函数的元数据,包括函数名、文档字符串等。

2. 保持装饰器的单一职责原则,一个装饰器只完成一个特定功能。

3. 为装饰器添加清晰的文档字符串,说明其用途和参数。

4. 在调试时,可以通过打印或日志记录来追踪装饰器的执行流程。

5. 对于复杂的装饰器逻辑,考虑使用类装饰器以更好地管理状态。

通过掌握类装饰器和带参数装饰器,你将能够编写出更加灵活和强大的 Python 代码,这些技术在实际项目开发中非常实用。

相关文章

[Python 教程] Matplotlib 数据可视化教程

Matplotlib 数据可视化教程 Matplotlib 是 Python 最常用的绘图库。本文介绍常用图表的绘制方法。 一、基础设置 import matplotlib.pyplot as pl...

Python 装饰器实用技巧:从入门到精通

装饰器是 Python 最强大的特性之一,但也是很多开发者感到困惑的概念。简单来说,装饰器是一个函数,它接受另一个函数作为输入,并返回一个新的函数。使用装饰器,你可以在不修改原函数代码的情况下,为其添...

Python 装饰器的 5 个实用场景:从入门到精通

装饰器(Decorator)是 Python 中的"函数包装器",它允许我们在不修改原函数代码的前提下,动态地添加功能。很多初学者学完 @decorator 语法后就止步不前,但实际上装饰器在实际工程...

Python 装饰器实战:从入门到精通

装饰器本质上是一个接受函数作为参数并返回新函数的高阶函数。理解这一点是掌握装饰器的关键。当你看到@decorator 语法时,Python 实际上是在执行 func = decorator(func)...

Python 中利用 functools.lru_cache 实现高效缓存:从入门到进阶

Python 中利用 functools.lru_cache 实现高效缓存:从入门到进阶 在日常 Python 开发中,我们经常会遇到重复计算相同输入的问题,比如递归计算斐波那契数列、多次调用相同参...

Python 生成器进阶:理解 yield 与构建高效迭代器

在 Python 开发中,我们经常需要处理大量数据或流式数据,如果一次性将所有数据加载到内存中,不仅会占用大量内存空间,还可能导致程序运行缓慢甚至崩溃。生成器(Generator)正是解决这个问题的利...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。