
本文详解为何在 Client 类中重写 __getattribute__ 会导致无限递归,以及如何通过 __getattr__ 安全实现委托模式,使客户端能直接调用被委托对象的方法(如 client.display()),同时兼容抽象工厂的动态硬件创建。
本文详解为何在 client 类中重写 `__getattribute__` 会导致无限递归,以及如何通过 `__getattr__` 安全实现委托模式,使客户端能直接调用被委托对象的方法(如 `client.display()`),同时兼容抽象工厂的动态硬件创建。
在 Python 设计模式实践中,将抽象工厂模式(用于解耦对象创建)与委托模式(用于行为转发)结合是提升代码灵活性的常见尝试。但若委托实现不当,极易触发 RecursionError: maximum recursion depth exceeded —— 正如示例中 client_with_laptop.display() 调用时所见。根本原因在于对特殊方法 __getattribute__ 的误用。
❌ 错误根源:__getattribute__ 的无条件拦截
__getattribute__ 是 Python 中最底层的属性访问钩子,每次访问任意属性(包括 self._hardware、内置方法、甚至 __dict__)都会触发它。原代码中:
def __getattribute__(self, name: str):
return getattr(self._hardware, name) # ⚠️ 问题在此!当执行 client.display() 时:
- 触发 client.__getattribute__('display')
- 该方法试图读取 self._hardware(即 getattr(self._hardware, 'display'))
- 但读取 self._hardware 本身又会再次调用 client.__getattribute__('_hardware')
- → 无限循环,直至栈溢出
更隐蔽的是:即使 self._hardware 已存在,__getattribute__ 在查找 _hardware 属性时仍需先访问 self.__dict__ 或父类逻辑,而这些内部操作同样受 __getattribute__ 管控,形成闭环。
立即学习“Python免费学习笔记(深入)”;
✅ 正确方案:使用 __getattr__ 实现“按需委托”
__getattr__ 仅在常规属性查找失败(即 AttributeError 抛出)后才被调用,天然规避了递归风险。它专为“委托未知属性”而设计,语义清晰且安全:
class Client:
def __init__(self, factory: IFactory) -> None:
self._hardware = factory.get_hardware() # ✅ 私有属性,不触发 __getattr__
def __getattr__(self, name: str):
# 仅当 self 没有该属性时,才委托给 _hardware
return getattr(self._hardware, name)此时调用流程变为:
- client.display() → 查找 client.display → 未找到 → 触发 __getattr__('display')
- __getattr__ 转发至 self._hardware.display → 成功返回绑定方法
- 访问 client._hardware 或 client.__dict__ 等现有属性时,完全绕过 __getattr__,无递归风险。
? 完整可运行示例(修复后)
from abc import ABC, abstractmethod
class ITechnique(ABC):
@abstractmethod
def display(self) -> None:
pass
def turn_on(self) -> None:
print("I am on!")
def turn_off(self) -> None:
print("I am off!")
class Laptop(ITechnique):
def display(self) -> None:
print("I'm a Laptop")
class Smartphone(ITechnique):
def display(self) -> None:
print("I'm a Smartphone")
class Tablet(ITechnique):
def display(self) -> None:
print("I'm a tablet!")
class IFactory(ABC):
@abstractmethod
def get_hardware(self) -> ITechnique:
pass
class SmartphoneFactory(IFactory):
def get_hardware(self) -> ITechnique:
return Smartphone()
class LaptopFactory(IFactory):
def get_hardware(self) -> ITechnique:
return Laptop()
class TabletFactory(IFactory):
def get_hardware(self) -> ITechnique:
return Tablet()
class Client:
def __init__(self, factory: IFactory) -> None:
self._hardware = factory.get_hardware() # 初始化硬件实例
def __getattr__(self, name: str):
# 仅当本对象无此属性时,委托给 _hardware
return getattr(self._hardware, name)
# ✅ 使用方式:直接调用,无需 ._hardware
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!⚠️ 关键注意事项
- 不要混用 __getattribute__ 和 __getattr__:除非有极特殊需求(如日志、权限控制),否则优先用 __getattr__ 实现委托。
- __getattr__ 不处理已存在的属性:self._hardware、self.__init__ 等均正常访问,委托仅作用于“缺失属性”。
- 委托不覆盖同名方法:若 Client 自定义了 display(),则 __getattr__ 不生效——这是预期行为,确保显式逻辑优先。
- 类型提示兼容性:静态检查器(如 mypy)可能无法推断委托方法。可通过 typing.overload 或文档明确说明委托接口,或考虑 @property + 显式代理方法提升可维护性。
✨ 总结
抽象工厂负责“创建什么”,委托模式负责“如何使用”。二者结合的关键在于:用 __getattr__ 做轻量、安全的动态委托,而非用 __getattribute__ 进行高危的全局拦截。这不仅解决了递归错误,更体现了 Python 数据模型的设计哲学——用最简机制达成目标。掌握这一区别,是深入理解 Python 面向对象与设计模式协同工作的基石。








