
本文详解为何在 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)失败后才被调用,天然规避了此问题。
立即学习“Python免费学习笔记(深入)”;
✅ 正确解法:使用 __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 元编程基础的重要实践。








