0

0

Python 中组合抽象工厂与委托模式时的递归错误解析与修复方案

霞舞

霞舞

发布时间:2026-03-03 17:43:02

|

598人浏览过

|

来源于php中文网

原创

Python 中组合抽象工厂与委托模式时的递归错误解析与修复方案

本文详解为何在 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() 时,流程如下:

  1. Python 调用 client.__getattribute__('display')
  2. 方法内执行 getattr(self._hardware, 'display') —— 这本身又是一次属性访问!
  3. getattr 内部会尝试获取 self._hardware(即 client._hardware),从而再次调用 client.__getattribute__('_hardware')
  4. 于是进入无限循环:__getattribute__ → getattr → __getattribute__ → ...,直至栈溢出。

⚠️ 关键认知:__getattribute__ 不区分“内部状态访问”与“外部接口转发”,它对所有属性一视同仁;而 __getattr__ 仅在常规查找(实例字典、类、父类 MRO)失败后才被调用,天然规避了此问题。

立即学习Python免费学习笔记(深入)”;

创客贴设计
创客贴设计

创客贴设计,一款智能在线设计工具,设计不求人,AI助你零基础完成专业设计!

下载

✅ 正确解法:使用 __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 元编程基础的重要实践。

热门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号