Python 装饰器进阶:带参数装饰器与类装饰器实战
装饰器(Decorator)是 Python 中最优雅的特性之一,它允许我们在不修改原函数代码的情况下,为函数添加额外功能。上一篇我们学习了基础装饰器,今天将深入探讨两个进阶主题:带参数的装饰器和类装饰器。
## 带参数的装饰器
基础装饰器只能接收被装饰的函数,但有时我们希望装饰器也能接收参数。例如,创建一个可配置的重试装饰器:
def retry(max_attempts=3, delay=1):\n \"\"\"带参数的装饰器工厂函数\"\"\"\n import time\n \n def decorator(func):\n def wrapper(*args, **kwargs):\n last_exception = None\n for attempt in range(1, max_attempts 1):\n try:\n return func(*args, **kwargs)\n except Exception as e:\n last_exception = e\n if attempt < max_attempts:\n print(f\"第 {attempt} 次尝试失败, {delay}秒后重试...\")\n time.sleep(delay)\n raise last_exception\n return wrapper\n return decorator\n使用带参数装饰器时,括号是必需的:
@retry(max_attempts=5, delay=2)\ndef fetch_data(url):\n import random\n if random.random() < 0.7:\n raise ConnectionError(\"网络连接失败\")\n return \"数据获取成功\"\n\n# 调用函数\nprint(fetch_data(\"https://api.example.com\"))\n## 类装饰器
装饰器不仅可以是函数,还可以是类。类装饰器使用 __call__ 方法来实现装饰功能:
class Timer:\n \"\"\"类装饰器,用于测量函数执行时间\"\"\"\n \n def __init__(self, func):\n self.func = func\n \n def __call__(self, *args, **kwargs):\n import time\n start_time = time.time()\n result = self.func(*args, **kwargs)\n end_time = time.time()\n print(f\"{self.func.__name__} 执行时间: {end_time - start_time:.4f}秒\")\n return result\n使用类装饰器时,不需要括号:
@Timer\ndef fibonacci(n):\n if n <= 1:\n return n\n return fibonacci(n-1) fibonacci(n-2)\n\nprint(fibonacci(30))\n## 带参数的类装饰器
如果需要带参数的类装饰器,需要一个两层嵌套结构:
class RateLimit:\n \"\"\"带参数的类装饰器,实现调用频率限制\"\"\"\n \n def __init__(self, calls_per_second=1):\n self.calls_per_second = calls_per_second\n self.last_call_time = 0\n \n def __call__(self, func):\n def wrapper(*args, **kwargs):\n import time\n current_time = time.time()\n time_since_last_call = current_time - self.last_call_time\n \n if time_since_last_call < (1.0 / self.calls_per_second):\n sleep_time = (1.0 / self.calls_per_second) - time_since_last_call\n print(f\"达到频率限制,等待 {sleep_time:.2f}秒...\")\n time.sleep(sleep_time)\n \n self.last_call_time = time.time()\n return func(*args, **kwargs)\n return wrapper\n使用带参数的类装饰器:
@RateLimit(calls_per_second=2)\ndef api_request(request_id):\n print(f\"处理请求 #{request_id}\")\n \nfor i in range(5):\n api_request(i 1)\n## 装饰器叠加
多个装饰器可以叠加使用,执行顺序是从内到外(从下到上):
@retry(max_attempts=3)\n@Timer\ndef process_file(filepath):\n import random\n if random.random() < 0.5:\n raise IOError(\"文件读取错误\")\n print(f\"处理文件: {filepath}\")\n return \"处理完成\"\n\nprocess_file(\"data.txt\")\n## 实战案例:缓存装饰器
下面是一个实用的缓存装饰器,可以缓存函数结果避免重复计算:
def cache(max_size=128):\n \"\"\"带参数的缓存装饰器\"\"\"\n from functools import lru_cache\n \n def decorator(func):\n return lru_cache(maxsize=max_size)(func)\n return decorator\n\n@cache(max_size=64)\ndef expensive_computation(n):\n print(f\"计算 {n} 的平方...\")\n return n ** 2\n\n# 第一次调用会执行计算\nprint(expensive_computation(5))\n\n# 第二次调用从缓存读取\nprint(expensive_computation(5))\n## 保留原函数元数据
装饰器会覆盖原函数的 __name__、__doc__ 等属性。使用 functools.wraps 可以保留这些元数据:
from functools import wraps\n\ndef log_function_call(func):\n @wraps(func)\n def wrapper(*args, **kwargs):\n print(f\"调用函数: {func.__name__}\")\n return func(*args, **kwargs)\n return wrapper\n\n@log_function_call\ndef calculate_area(radius):\n \"\"\"计算圆的面积\"\"\"\n return 3.14159 * radius ** 2\n\nprint(calculate_area.__name__) # 输出: calculate_area\nprint(calculate_area.__doc__) # 输出: 计算圆的面积\n## 总结
带参数装饰器通过"装饰器工厂函数"模式实现,需要三层嵌套结构。类装饰器使用 __call__ 魔术方法,更面向对象。两者都可以与基础装饰器自由组合,创造出强大的功能。
掌握这些进阶技巧后,你可以编写更灵活、更可复用的装饰器,为代码添加日志、缓存、权限验证、性能监控等功能,而无需修改业务逻辑代码。
