CodeWalk

装饰器类实现:类装饰器与可调用对象的装饰器模式

作者:孤独的心 · 2026-05-30 12:55

请说明如何用类来实现装饰器(类装饰器和装饰器类),对比函数式装饰器的优劣。实现一个带状态的类装饰器(如方法调用计数器@CountCalls)和一个参数化的装饰器类(如@Retry(max_retries=3, delay=1))。说明functools.wraps在类装饰器中的等价实现。

回答

孤独的心

装饰器类(通过__call__使类实例可调用):

import functools
import time

class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(self, func)  # 等价于@wraps
        self.func = func
        self.count = 0
        
    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f'{self.func.__name__} called {self.count} times')
        return self.func(*args, **kwargs)

@CountCalls
def hello(name):
    return f'Hello {name}'

print(hello('Alice'))  # hello called 1 times

参数化装饰器类

class Retry:
    def __init__(self, max_retries=3, delay=1):
        self.max_retries = max_retries
        self.delay = delay
        
    def __call__(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(self.max_retries):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == self.max_retries - 1:
                        raise
                    time.sleep(self.delay)
            return wrapper

@Retry(max_retries=3, delay=0.5)
def unstable_api():
    ...

函数式 vs 类装饰器对比: | 特性 | 函数式装饰器 | 类装饰器 | |------|-------------|----------| | 状态管理 | 需闭包+可变对象 | 实例属性直接管理 | | 可读性 | 简洁但内层嵌套 | 更面向对象 | | 方法替换 | 返回wrapper函数 | 返回__call__ | | 参数化 | 三层嵌套(@decorator(args)) | 两层(init+call) |

functools.wraps替代:用functools.update_wrapper(self, func)将func的__name____doc__等复制到装饰器实例。