
本文详解在 Python 中通过 __getattribute__ 实现委托模式时,为何会因无限递归触发 RecursionError,并提供安全、专业的替代实现(使用 __getattr__),同时保持抽象工厂的灵活性与客户端调用的简洁性。
本文详解在 python 中通过 `__getattribute__` 实现委托模式时,为何会因无限递归触发 `recursionerror`,并提供安全、专业的替代实现(使用 `__getattr__`),同时保持抽象工厂的灵活性与客户端调用的简洁性。
在设计模式实践中,将抽象工厂(Abstract Factory)与委托(Delegation)结合是一种常见学习目标:前者负责动态创建具体硬件对象(如 Laptop、Smartphone),后者则希望让客户端(Client)无需显式访问 _hardware 属性,即可直接调用其方法(如 client.display())。然而,若直接重写 __getattribute__ 实现委托,极易引发致命的递归崩溃——正如示例中报出的 RecursionError: maximum recursion depth exceeded。
问题根源在于 __getattribute__ 的执行时机:它会在 每一次 属性访问(包括对 self._hardware 的读取)时无条件触发。观察原 Client.__getattribute__ 实现:
def __getattribute__(self, name: str):
return getattr(self._hardware, name) # ⚠️ 此行再次触发 __getattribute__!当执行 client.display() 时:
- 解释器调用 client.__getattribute__('display');
- 方法内部尝试读取 self._hardware → 触发 client.__getattribute__('_hardware');
- 新一轮调用又试图读取 _hardware → 再次触发……
如此形成无法终止的自我调用链,最终耗尽栈空间。
✅ 正确解法:改用 __getattr__
__getattr__ 仅在常规属性查找失败(即该属性 不存在于实例字典或类继承链中)时才被调用,天然规避了对 _hardware 等自有属性的干扰。这是实现“安全委托”的标准实践:
立即学习“Python免费学习笔记(深入)”;
class Client:
def __init__(self, factory: IFactory) -> None:
self._hardware = factory.get_hardware() # ✅ 私有属性,不触发 __getattr__
def __getattr__(self, name: str):
# 仅当 client 自身没有该属性时,才委托给 _hardware
return getattr(self._hardware, name)现在验证效果:
if __name__ == "__main__":
client_with_laptop = Client(LaptopFactory())
client_with_laptop.display() # → "I'am a Laptop" (无递归!)
client_with_laptop.turn_on() # → "I am on!"
# 同时仍可安全访问自有属性
print(hasattr(client_with_laptop, '_hardware')) # True
print(client_with_laptop._hardware) # <__main__.Laptop object at ...>? 关键注意事项:
- 勿混用 __getattribute__ 与 __getattr__:除非有极特殊需求(如全局日志、权限拦截),否则优先选择 __getattr__ 实现委托;
- 避免委托私有/特殊方法:若需代理 __str__、__len__ 等魔术方法,需显式在 __getattr__ 中处理(因其可能被 __getattribute__ 优先捕获);
- 类型提示兼容性:__getattr__ 不影响静态类型检查器(如 mypy)对已知属性的推断,但对动态委托的方法需配合 # type: ignore 或协议(Protocol)增强可维护性。
总结而言,抽象工厂负责“创建什么”,委托模式负责“如何暴露行为”。二者协同的关键在于:用 __getattr__ 守住委托的边界,让委托只发生在真正需要时,而非每次属性访问都盲目转发。 这不仅是解决递归错误的技术方案,更是理解 Python 数据模型中属性访问机制的深度实践。










