
本文详解为何在 Python 中通过 __getattribute__ 实现委托模式时会触发无限递归,揭示 __getattribute__ 与 __getattr__ 的关键区别,并提供安全、可维护的委托实现方式。
本文详解为何在 python 中通过 `__getattribute__` 实现委托模式时会触发无限递归,揭示 `__getattribute__` 与 `__getattr__` 的关键区别,并提供安全、可维护的委托实现方式。
在面向对象设计中,将抽象工厂模式(用于创建一族相关对象)与委托模式(将方法调用转发给内部对象)结合,是理解职责分离与动态行为复用的绝佳实践。但若实现不当,极易陷入隐蔽而致命的递归陷阱——正如示例中调用 client_with_laptop.display() 时抛出的 RecursionError: maximum recursion depth exceeded。
问题根源在于对 __getattribute__ 的误用。该方法是 Python 属性访问的 最底层钩子 : 每次 访问任意属性(包括 self._hardware、self.__dict__、甚至方法名如 display)时,都会无条件触发它。而在示例的 Client.__getattribute__ 中,代码写为:
def __getattribute__(self, name: str): return getattr(self._hardware, name)
当执行 client.display() 时,流程如下:
- Python 调用 client.__getattribute__(‘display’)
- 方法内执行 getattr(self._hardware, ‘display’) —— 这本身又是一次属性访问!
- getattr 内部会尝试获取 self._hardware(即 client._hardware),从而再次调用 client.__getattribute__(‘_hardware’)
- 于是进入无限循环:__getattribute__ → getattr → __getattribute__ → …,直至栈溢出。
⚠️ 关键认知:__getattribute__ 不区分“内部状态访问”与“外部接口转发”,它对所有属性一视同仁;而 __getattr__ 仅在常规查找(实例字典、类、父类 MRO)失败后 才被调用,天然规避了此问题。
✅ 正确解法:使用 __getattr__ 实现安全委托
只需将 __getattribute__ 替换为 __getattr__,即可优雅解决递归问题:
class Client: def __init__(self, factory: IFactory) -> None: self._hardware = factory.get_hardware() # ✅ 私有属性正常存储 def __getattr__(self, name: str): # ⚠️ 仅当 client 本身没有 name 属性时才触发 return getattr(self._hardware, name)
此时调用链变为:
- client.display() → 尝试找 client.display(不存在)→ 触发 __getattr__(‘display’)
- __getattr__ 转发至 self._hardware.display → 成功返回绑定方法
- client._hardware 访问不触发 __getattr__(因 _hardware 是实例属性,直接命中)→ 无递归
? 验证修复效果
完整可运行代码(修正版):
from abc import ABC, abstractmethod class ITechnique(ABC): @abstractmethod def display(self): …… def turn_on(self): print("I am on!") def turn_off(self): print("I am off!") class Laptop(ITechnique): def display(self): print("I'm a Laptop") class Smartphone(ITechnique): def display(self): print("I'm a Smartphone") class Tablet(ITechnique): def display(self): print("I'm a tablet!") class IFactory(ABC): @abstractmethod def get_hardware(self): …… class SmartphoneFactory(IFactory): def get_hardware(self): return Smartphone() class LaptopFactory(IFactory): def get_hardware(self): return Laptop() class TabletFactory(IFactory): def get_hardware(self): return Tablet() class Client: def __init__(self, factory: IFactory) -> None: self._hardware = factory.get_hardware() def __getattr__(self, name: str): # ✅ 安全委托:仅代理硬件对象拥有的属性 / 方法 return getattr(self._hardware, name) # 使用示例 if __name__ == "__main__": client_laptop = Client(LaptopFactory()) client_laptop.display() # 输出: I'm a Laptop client_laptop.turn_on() # 输出: I am on! client_tablet = Client(TabletFactory()) client_tablet.display() # 输出: I'm a tablet!
? 注意事项与最佳实践
- 永远优先考虑 __getattr__:除非需要拦截 所有 属性访问(如日志、权限校验),否则 __getattr__ 是委托场景的黄金标准。
- 避免在 __getattr__ 中访问自身可能缺失的属性:例如 self._hardware 必须在 __init__ 中确保已存在,否则会再次触发 __getattr__ 导致递归。
- 明确委托边界 :当前实现会将 所有 未定义属性转发给 _hardware。若需限制(如仅转发 ITechnique 接口方法),可添加白名单检查:
def __getattr__(self, name: str): if name in {'display', 'turn_on', 'turn_off'}: return getattr(self._hardware, name) raise AttributeError(f"'{type(self).__name__}' has no attribute '{name}'") - 兼容性提醒:__getattribute__ 是性能敏感操作,过度使用会影响整体属性访问速度;__getattr__ 仅在属性缺失时调用,开销极低。
通过本次分析可见,设计模式的组合并非简单拼接,而是对语言机制的深度理解。掌握 __getattribute__ 与 __getattr__ 的语义差异,是写出健壮委托逻辑的关键一步——这不仅是修复一个 RecursionError,更是夯实 Python 元编程基础的重要实践。






























