子类重写方法导致装饰器失效的根本原因是覆盖了父类已被装饰的函数对象;应通过super()调用、使用functools.wraps、或抽离核心逻辑为未装饰函数来解决。

子类重写方法后装饰器失效的典型现象
父类方法加了 @lru_cache、@property 或自定义装饰器,子类一重写就没了——不是报错,而是缓存不生效、属性变普通方法、日志不打印。根本原因:Python 中装饰器本质是函数替换,@decorator 等价于 func = decorator(func);子类写 def method(self): ... 时,直接覆盖了父类那个已被装饰过的函数对象,装饰器逻辑自然丢失。
用 super() 显式调用父类已装饰的方法
这是最直接、最可靠的做法,尤其适用于 @lru_cache、@cached_property 等依赖函数对象身份的装饰器:
- 子类方法体里不要重新实现逻辑,而是调用
super().method() - 确保父类方法本身已正确装饰,且子类不改变签名(否则
lru_cache的 key 会出错) - 若需在调用前后加额外逻辑,用
before/after模式包裹super().method()
from functools import lru_cache
<p>class Parent:
@lru_cache(maxsize=128)
def compute(self, x):
print("computing...")
return x ** 2</p><p>class Child(Parent):
def compute(self, x):</p><h1>保留父类缓存行为,同时加日志</h1><pre class="brush:python;toolbar:false;"> result = super().compute(x)
print("computed via parent")
return result
自定义装饰器必须支持继承:检查是否用了 functools.wraps
如果你写的装饰器在子类重写后失效,大概率是因为没用 @functools.wraps(func) 修饰内层包装函数:
- 缺少
wraps→ 被装饰函数的<strong>name</strong>、<strong>doc</strong>、<strong>annotations</strong>全丢 →super().method查不到原始签名 → 继承链断裂 - 所有自定义装饰器(尤其是带参数的)必须用
wraps包一层
from functools import wraps
<p>def log_call(func):
@wraps(func) # ← 这行不能少
def wrapper(*args, *<em>kwargs):
print(f"Calling {func.<strong>name</strong>}")
return func(</em>args, **kwargs)
return wrapper</p>
避免覆盖装饰器函数对象:不用“重写”,改用“扩展”
有些场景(比如需要修改部分逻辑)不适合纯 super() 调用。这时应放弃覆盖,转为组合:
- 把核心逻辑抽成独立函数(未装饰),父类方法只负责装饰 + 调用它
- 子类重写该核心函数,父类装饰器仍作用于同一入口点
class Parent:
@lru_cache()
def compute(self, x):
return self._compute_impl(x) # ← 装饰的是这个壳
<pre class="brush:python;toolbar:false;">def _compute_impl(self, x):
return x ** 2class Child(Parent): def _compute_impl(self, x): # ← 只重写内部逻辑 return (x + 1) ** 2
装饰器功能是否保留,关键不在语法上“写了什么”,而在运行时“调用的是哪个函数对象”。绕过对象覆盖、守住调用链,比任何黑科技都管用。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











