0

0

如何在 Python 中正确结合抽象工厂与委托模式避免递归错误

聖光之護

聖光之護

发布时间:2026-03-03 16:47:11

|

729人浏览过

|

来源于php中文网

原创

如何在 Python 中正确结合抽象工厂与委托模式避免递归错误

本文详解为何在 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() 时:

  1. 触发 client.__getattribute__('display')
  2. 该方法试图读取 self._hardware(即 getattr(self._hardware, 'display'))
  3. 但读取 self._hardware 本身又会再次调用 client.__getattribute__('_hardware')
  4. → 无限循环,直至栈溢出

更隐蔽的是:即使 self._hardware 已存在,__getattribute__ 在查找 _hardware 属性时仍需先访问 self.__dict__ 或父类逻辑,而这些内部操作同样受 __getattribute__ 管控,形成闭环。

人声去除
人声去除

用强大的AI算法将声音从音乐中分离出来

下载

立即学习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 面向对象与设计模式协同工作的基石。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

57

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

62

2025.11.27

go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

57

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

62

2025.11.27

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1772

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

569

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2338

2025.12.29

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

43

2026.01.19

Swift iOS架构设计与MVVM模式实战
Swift iOS架构设计与MVVM模式实战

本专题聚焦 Swift 在 iOS 应用架构设计中的实践,系统讲解 MVVM 模式的核心思想、数据绑定机制、模块拆分策略以及组件化开发方法。内容涵盖网络层封装、状态管理、依赖注入与性能优化技巧。通过完整项目案例,帮助开发者构建结构清晰、可维护性强的 iOS 应用架构体系。

2

2026.03.03

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.5万人学习

Django 教程
Django 教程

共28课时 | 4.7万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号