
本文介绍一种通过自定义 __getattr__ 并结合描述符协议(如 property 和绑定方法)实现包装类透明覆盖被包装对象属性的技巧,使被包装对象的方法在运行时自动使用包装类的属性值。
本文介绍一种通过自定义 `__getattr__` 并结合描述符协议(如 `property` 和绑定方法)实现包装类透明覆盖被包装对象属性的技巧,使被包装对象的方法在运行时自动使用包装类的属性值。
在 Python 中,当使用包装器(wrapper)模式对一个类进行封装时,常面临一个关键限制:被包装对象内部的方法仍会访问其自身的属性,而非包装类中重定义的同名属性。这是因为方法的 self 指向的是原始对象(如 A 实例),而非包装器(B 实例)。默认的属性委托机制(如 __getattr__)仅在属性未在 B 中找到时才触发,但无法影响 A.method() 内部对 self.prop 的解析——它始终绑定到 A 实例。
要突破这一限制,核心思路是:让 B 在代理 A 的方法和属性时,主动“替换上下文”,即:
- 对
A的方法,将其底层函数(__func__)重新绑定到B实例,使方法体内的self指向B; - 对
A的@property,不直接返回A的属性对象,而是调用其fget方法,并传入B实例作为self参数,从而让fget(self)中的self指向B,进而读取B.prop。
以下是完整的、可直接使用的 B 类实现:
from inspect import ismethod
class B:
def __init__(self, a: 'A'):
self._a = a
@property
def prop(self) -> int:
return 2 # 覆盖 A.prop
@property
def other_prop(self) -> int:
return self.prop * 4 # 依赖自身 prop,确保一致性
def __getattr__(self, attr):
# 1. 先尝试从 A 的类(type(_a))中获取 property 描述符
cls_attr = getattr(type(self._a), attr, None)
if isinstance(cls_attr, property):
# 调用 property 的 getter,传入 self(即 B 实例)
return cls_attr.fget(self)
# 2. 再从 A 实例获取属性值
value = getattr(self._a, attr)
# 3. 若为绑定方法,解包并重新绑定到 B 实例
if ismethod(value):
value = value.__func__.__get__(self)
return value✅ 效果验证:
class A:
@property
def prop(self) -> int:
return 1
@property
def other_prop(self) -> int:
return self.prop * 4
def method(self) -> int:
return self.prop * 2
a = A()
b = B(a)
print(b.prop) # → 2
print(b.other_prop) # → 8 (因 b.prop == 2,故 2 * 4)
print(b.method()) # → 4 (因 method 中 self.prop → b.prop == 2)⚠️ 注意事项与边界说明:
- 此方案依赖
property和普通方法的描述符行为,不适用于@cached_property或自定义描述符(需额外适配); - 若
A中存在非property的实例属性(如self._prop = 1),该方案不会覆盖——因为__getattr__不介入已存在的实例属性查找;如需完全拦截,需重写__getattribute__,但代价更高且易出错; - 所有被覆盖的属性(如
prop,other_prop)必须在B中明确定义为@property,否则fget(self)将失败; -
__getattr__仅对未在B中定义的属性生效,因此B必须显式声明所有需覆盖的属性,这是设计上的明确契约。
? 总结:该方案以最小侵入性达成目标——无需修改 A、不使用继承、不复制业务逻辑,仅通过精准控制属性访问链,使包装器真正成为“语义上替代原始对象”的代理。它体现了 Python 描述符协议的强大灵活性,是高级包装器设计的典型实践。

















