Python 组合优于继承的实践示例

发布时间 - 2026-01-30 00:00:00    点击率:
该用 class A 包含 B() 而不是 class A(B) 当只需部分父类行为且需灵活替换、测试隔离或避免强耦合时;继承会绑定全部接口与生命周期

,易因 B 变更导致 A 失效。

什么时候该用 class A 包含 B() 而不是 class A(B)

当子类只需要部分父类行为,且这些行为可能随上下文变化、需要替换或测试隔离时,组合更合适。继承会把 B 的全部接口和生命周期强绑定到 A,一旦 B 内部改了(比如加了新方法或修改了 __init__ 参数),A 就可能意外出错。

常见信号包括:

  • 你只调用 B 的 1–2 个方法,却继承了它全部 20 个方法和属性
  • A 的测试需要 mock B 的行为——用继承时只能 patch 类方法或绕过 super().__init__,而组合直接传入 mock 实例即可
  • B 是第三方类(如 requests.Sessionsqlite3.Connection),你无法控制其演进

__init__ 里传实例比在类里硬编码 B() 更灵活

硬编码 self.db = Database() 会让单元测试难写,也堵死了换存储后端的路。应该把依赖显式传进来,哪怕默认给一个。

class UserService:
    def __init__(self, db=None):
        self.db = db or Database()  # 可选,默认构造

这样调用时可以自由替换:

  • 测试: UserService(MockDB())
  • 开发: UserService(InMemoryDB())
  • 生产: UserService(PostgresDB(host="..."))

注意:不要在 __init__ 里做重操作(如连接数据库),否则组合对象初始化失败时难以定位是自身问题还是依赖问题。

用属性委托代替继承后的方法转发

继承后如果只转发几个方法(比如 def save(self, *a): return self.db.save(*a)),不如用 __getattr__ 动态代理——减少样板代码,还能统一处理未实现方法。

class Repository:
    def __init__(self, backend):
        self._backend = backend
def __getattr__(self, name):
    return getattr(self._backend, name)

但要注意:

  • __getattr__ 不拦截已定义的属性或方法(如 __init____str__),所以不会覆盖你自己的逻辑
  • 如果 _backend 某方法抛 AttributeError,会被吞掉并转抛为 Repository 的 AttributeError,调试时容易误判来源
  • IDE 和类型检查器(如 mypy)可能无法推导动态代理的方法,需配 typing.overloadProtocol

组合后别忘了生命周期管理

继承时,父类资源通常和子类共生死;组合则要自己管。比如 self.session = requests.Session(),你得确保它被关闭——否则连接泄漏。

  • 用上下文管理器:with UserService(db) as service:,在 __enter__/__exit__ 中控制 db.close()
  • 或者暴露显式清理方法:service.close(),并在文档里强调“必须调用”
  • 避免在 __del__ 里关资源——不可靠,且可能触发循环引用

最易忽略的是:组合对象被长期持有(如全局单例或 Flask 应用中的 g 对象)时,底层依赖的连接/文件句柄是否复用合理、有没有超时重连逻辑。


# python  # 编码  # session  # 后端  # 动态代理  # flask  # 父类  # 子类 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 音响网站制作视频教程,隆霸音响官方网站?  专业企业网站设计制作公司,如何理解商贸企业的统一配送和分销网络建设?  香港服务器租用费用高吗?如何避免常见误区?  制作无缝贴图网站有哪些,3dmax无缝贴图怎么调?  iOS正则表达式验证手机号、邮箱、身份证号等  Laravel如何与Inertia.js和Vue/React构建现代单页应用  uc浏览器二维码扫描入口_uc浏览器扫码功能使用地址  Laravel如何实现API版本控制_Laravel API版本化路由设计策略  Laravel怎么实现验证码功能_Laravel集成验证码库防止机器人注册  Windows10如何更改计算机工作组_Win10系统属性修改Workgroup  Laravel如何使用软删除(Soft Deletes)功能_Eloquent软删除与数据恢复方法  Laravel如何实现密码重置功能_Laravel密码找回与重置流程  如何在阿里云香港服务器快速搭建网站?  猪八戒网站制作视频,开发一个猪八戒网站,大约需要多少?或者自己请程序员,需要什么程序员,多少程序员能完成?  个人摄影网站制作流程,摄影爱好者都去什么网站?  品牌网站制作公司有哪些,买正品品牌一般去哪个网站买?  iOS发送验证码倒计时应用  百度输入法ai面板怎么关 百度输入法ai面板隐藏技巧  Laravel如何集成微信支付SDK_Laravel使用yansongda-pay实现扫码支付【实战】  网页设计与网站制作内容,怎样注册网站?  Laravel怎么创建自己的包(Package)_Laravel扩展包开发入门到发布  如何实现javascript表单验证_正则表达式有哪些实用技巧  如何快速查询网站的真实建站时间?  Python文件异常处理策略_健壮性说明【指导】  如何有效防御Web建站篡改攻击?  Win11怎么关闭专注助手 Win11关闭免打扰模式设置【操作】  如何快速建站并高效导出源代码?  如何快速搭建高效香港服务器网站?  Laravel如何实现数据库事务?(DB Facade示例)  桂林网站制作公司有哪些,桂林马拉松怎么报名?  微信小程序 input输入框控件详解及实例(多种示例)  ,交易猫的商品怎么发布到网站上去?  网页制作模板网站推荐,网页设计海报之类的素材哪里好?  轻松掌握MySQL函数中的last_insert_id()  Laravel如何实现全文搜索_Laravel Scout集成Algolia或Meilisearch教程  如何将凡科建站内容保存为本地文件?  JS弹性运动实现方法分析  HTML5空格在Angular项目里怎么处理_Angular中空格的渲染问题【详解】  悟空识字如何进行跟读录音_悟空识字开启麦克风权限与录音  laravel怎么实现图片的压缩和裁剪_laravel图片压缩与裁剪方法  Laravel如何实现用户注册和登录?(Auth脚手架指南)  免费制作统计图的网站有哪些,如何看待现如今年轻人买房难的情况?  如何快速完成中国万网建站详细流程?  三星、SK海力士获美批准:可向中国出口芯片制造设备  Android 常见的图片加载框架详细介绍  JavaScript Ajax实现异步通信  1688铺货到淘宝怎么操作 1688一键铺货到自己店铺详细步骤  python中快速进行多个字符替换的方法小结  Swift中switch语句区间和元组模式匹配  如何确认建站备案号应放置的具体位置?