类装饰器必须实现__call__方法才能被调用,否则报typeerror;__init__接收装饰器参数,__call__接收被装饰函数并返回可调用对象,需用@functools.wraps保留元信息。

类装饰器必须实现 __call__ 方法才能被调用
Python 解释器在遇到 @MyDecorator 语法时,会尝试把被装饰对象传给 MyDecorator 的实例(即调用 __call__)。如果类没定义这个方法,就会报 TypeError: 'MyDecorator' object is not callable。
常见错误是只写了 __init__ 和 __enter__ 之类的方法,忘了加 __call__ —— 这会导致装饰器“静默失效”,函数照常运行但扩展逻辑完全不触发。
-
__init__用于接收装饰器参数(比如@Timer(log_level="debug")) -
__call__必须接收被装饰的函数,并返回一个可调用对象(通常是闭包或新函数) - 若需保留原函数元信息(如
__name__、__doc__),要在返回的包装函数上加@functools.wraps(func)
带参数的类装饰器要嵌套两层 __call__
当装饰器需要接收配置参数(如 @Retry(max_attempts=3)),类本身不能直接接收函数,而要先返回一个“装饰器工厂”实例。这要求类的 __call__ 返回另一个可调用对象,该对象再接收被装饰函数。
典型结构是:外层 __call__ 处理装饰器参数,内层闭包(或子类实例)处理目标函数。漏掉其中一层,就会出现 TypeError: __call__() takes 1 positional argument but 2 were given。
- 第一层
__call__(self, *args, **kwargs):接收装饰器参数,返回第二层可调用对象 - 第二层(通常是个闭包):
def wrapper(func): ... return wrapper,接收被装饰函数 - 不要试图在第一层直接接收
func;那属于无参装饰器场景
类装饰器中访问实例状态比函数装饰器更自然
函数装饰器若要跨调用保存状态(如统计调用次数),得依赖闭包变量或模块级变量,容易引发多线程问题或测试污染。类装饰器天然拥有实例属性,状态隔离更直观。
例如实现一个计数器,用 self.count = 0 比用 nonlocal count 更清晰,也更容易在测试中重置或断言。
- 每个被装饰函数对应一个独立的类实例(除非显式复用),状态互不干扰
- 若需全局共享状态(如统一日志 handler),应放在类变量或外部对象中,而非实例属性
- 注意:类实例在装饰阶段就创建了,不是每次函数调用都新建;所以
__init__中的逻辑只执行一次
装饰器类名和实例名别和被装饰函数同名,否则覆盖原函数
写成 timer = Timer() 然后 @timer 是安全的;但若写成 Timer = Timer(),后续再 @Timer 就会丢失类定义,导致后续无法实例化新装饰器。
更隐蔽的问题是,在模块顶层直接 my_decorator = MyDecorator() 并重复使用,看似省事,实则所有被它装饰的函数共享同一实例——如果装饰器有可变状态(如缓存字典),就可能互相污染。
- 推荐写法:
@MyDecorator()(每次新建实例),或明确命名如cache_decorator = Cache(maxsize=128) - 避免
Timer = Timer()、retry = Retry()这类赋值,尤其在模块作用域 - 调试时可用
print(type(decorator_instance))确认是否还是类本身,而不是已被实例覆盖
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











