装饰器工厂与类装饰器:进阶装饰器技术解析
装饰器是Python中强大的元编程工具,除多种进阶形式。其中装饰器工厂(Decorator Factory)和类装饰器(Class Decorator)是两种重要变体,分别解决“带参数的装饰器”和“复杂状态管理的装饰器”问题。下面详细解析两者的原理、用法和区别。
一、装饰器工厂:带参数的装饰器生成器
装饰器工厂本质是“生成装饰器的函数”,它接受参数并返回一个装饰器(通常是函数装饰器),用于解决“装饰器需要自定义参数”的场景(如控制装饰器的行为细节)。
1. 为什么需要装饰器工厂?
普通装饰器(如之前的计时装饰器)无法直接接收参数,若想让装饰器更灵活(例如控制日志格式、设置超时时间),就需要用装饰器工厂“动态生成”带有参数的装饰器。
例如:希望计时装饰器能接收一个参数 unit
,控制输出时间的单位(秒/毫秒)。
2. 装饰器工厂的实现原理
装饰器工厂的结构是“函数嵌套三层”:
- 外层函数:装饰器工厂,接收装饰器的参数;
- 中层函数:真正的装饰器,接收被装饰的函数;
- 内层函数:闭包(
wrapper
),包裹原函数执行逻辑,使用外层的参数。
# 装饰器工厂:接收参数,返回装饰器
def timer_factory(unit="second"): # 外层:装饰器工厂,接收参数 unit# 中层:真正的装饰器,接收被装饰函数def timer_decorator(func):# 内层:闭包,使用外层的 unit 参数def wrapper(*args, **kwargs):import timestart = time.time()result = func(*args, **kwargs)end = time.time()duration = end - start# 根据 unit 参数格式化输出if unit == "ms":print(f"{func.__name__} 耗时:{duration*1000:.2f} 毫秒")else:print(f"{func.__name__} 耗时:{duration:.2f} 秒")return resultreturn wrapper # 返回闭包return timer_decorator # 返回装饰器# 使用装饰器工厂:先传参数生成装饰器,再装饰函数
@timer_factory(unit="ms") # 等价于:@生成的 timer_decorator
def slow_function(n):import timetime.sleep(n)return nslow_function(1) # 输出:slow_function 耗时:1001.23 毫秒(单位由参数控制)
3. 执行流程解析
- 调用
timer_factory(unit="ms")
,返回一个定制化的装饰器timer_decorator
(该装饰器“记住”了unit="ms"
这个参数); @timer_decorator
作用于slow_function
,即slow_function = timer_decorator(slow_function)
;- 调用
slow_function(1)
时,执行闭包wrapper
,使用unit="ms"
格式化输出。
二、类装饰器:基于类的装饰器实现
类装饰器是用类实现的装饰器,通过类的特殊方法(__init__
和 __call__
)实现装饰逻辑,尤其适合需要管理复杂状态(如计数、缓存)的场景。
1. 类装饰器的核心要素
要实现类装饰器,需满足:
__init__
方法:接收被装饰的函数作为参数,保存原函数引用(类似函数装饰器接收原函数);__call__
方法:实现装饰逻辑,包裹原函数执行(类似闭包wrapper
的作用),可通过实例属性保存状态。
2. 基础示例:带状态的类装饰器
下面实现一个统计函数调用次数的类装饰器,展示其状态管理能力:
class CallCounter:# 初始化:接收被装饰函数,初始化状态(计数)def __init__(self, func):self.func = func # 保存原函数self.count = 0 # 实例属性:记录调用次数(状态持久化)# 调用:实现装饰逻辑,每次调用计数+1def __call__(self, *args, **kwargs):self.count += 1print(f"[{self.func.__name__}] 第 {self.count} 次调用")return self.func(*args, **kwargs) # 调用原函数# 使用类装饰器
@CallCounter
def add(a, b):return a + badd(1, 2) # 输出:[add] 第 1 次调用 → 返回 3
add(3, 4) # 输出:[add] 第 2 次调用 → 返回 7
优势:实例属性 self.count
自然支持状态持久化,无需像函数装饰器那样使用 nonlocal
关键字。
3. 带参数的类装饰器
类装饰器也可以通过“类工厂”的方式支持参数(类似装饰器工厂的思路),即先定义一个接收参数的类,再返回一个装饰器类。
例如,让计数装饰器支持“自定义前缀文本”:
# 类工厂:接收参数,返回定制化的类装饰器
def counter_factory(prefix="调用次数"):class CustomCallCounter:def __init__(self, func):self.func = funcself.count = 0self.prefix = prefix # 使用工厂参数def __call__(self, *args, **kwargs):self.count += 1print(f"[{self.func.__name__}] {self.prefix}:{self.count}")return self.func(*args, **kwargs)return CustomCallCounter # 返回定制化的类装饰器# 使用带参数的类装饰器
@counter_factory(prefix="第 N 次执行")
def multiply(a, b):return a * bmultiply(2, 3) # 输出:[multiply] 第 N 次执行:1 → 返回 6
multiply(4, 5) # 输出:[multiply] 第 N 次执行:2 → 返回 20
三、装饰器工厂与类装饰器的对比
维度 | 装饰器工厂(函数式) | 类装饰器(基于类) |
---|---|---|
核心结构 | 三层函数嵌套(工厂→装饰器→闭包) | 类 + __init__ + __call__ 方法 |
状态管理 | 依赖闭包的非局部变量(nonlocal ) |
依赖实例属性(self.xxx ),更直观 |
支持参数 | 原生支持(外层函数接收参数) | 需要通过“类工厂”间接支持 |
适用场景 | 轻量参数定制(如格式、开关) | 复杂状态管理(如计数、缓存、权限) |
可读性 | 简单场景清晰,复杂场景嵌套过深 | 结构清晰,适合扩展(如继承) |
四、总结
-** 装饰器工厂:是“生成装饰器的函数”,通过三层嵌套实现带参数的装饰器,适合需要灵活定制装饰逻辑的场景(如控制输出格式)。
- 类装饰器**:用类实现的装饰器,通过 __init__
和 __call__
方法工作,优势在于状态管理(实例属性自然保存状态),适合复杂逻辑(如计数、缓存)。
两者都是装饰器模式的进阶形式,选择哪种取决于需求:简单参数定制用装饰器工厂,复杂状态管理用类装饰器。理解它们的原理,能让你在实际开发中更灵活地使用装饰器解决问题。