当前位置: 首页 > news >正文

Python中继承带来的问题

继承——瑕瑜互见!

多重继承带来的菱形问题

我们知道Python是支持多重继承的,这就带来一个问题,当一个类C继承A和B的时候,而A,B又继承Base类,且都重写了Base的say方法,这个时候C类的实例调用say方法的时候,应该执行哪个?

在Python中可以通过__mro__方法知道调用的顺序。

class Base: def say(self): print("Base") class A(Base): def say(self): print("A") class B(Base): def say(self): print("B") class C(A, B): # 继承顺序 A 在前,B 在后 pass # 查看调用顺序 print(C.__mro__) c = C() c.say() # 输出 "A" # 因为 Python 的 MRO 决定了先找 A。

上面的C类的mro打印出来结果是(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>)。 这可能会让你误以为方法解析顺序使用的是广度优先搜索,其实不然,Python方法解析顺序使用的是公开发布的 C3 算法计算。请注意,除非你大量使用多重继承,或者继承关系不同寻常,否则无须了解 C3 算法。

C3算法其实只是保证了这三件事:

  • 子类永远在父类前面。
  • 继承列表里左边的在右边前面 。
  • 如果发生冲突(也就是菱形结构的顶端),共同的父类要等到所有子路径都汇合后才出现。

混入类(Mixin Classes)

混入类就好像一个插件,给目标类提供可选的功能。请注意是可选的!

混入类不能作为具体类的唯一基类,因为混入类不为具体对象提供全部功能,而是增加或定制子类或同级类的行为。

下面通过一个简单的例子来演示一下混入类的使用:我们要实现一个不区分大小写Key的字典。

from collections import UserDict class CaseInsensitiveMixin: """ 一个混入类,将所有字符串键转换为小写。 它不存储数据,只负责劫持并处理键的逻辑。 """ def _normalize_key(self, key): return key.lower() if isinstance(key, str) else key def __getitem__(self, key): return super().__getitem__(self._normalize_key(key)) def __setitem__(self, key, value): super().__setitem__(self._normalize_key(key), value) def __delitem__(self, key): super().__delitem__(self._normalize_key(key)) def __contains__(self, key): return super().__contains__(self._normalize_key(key)) class CaseInsensitiveDict(CaseInsensitiveMixin, UserDict): """ 通过多重继承,将 Mixin 的“不区分大小写”功能 混入到标准的 UserDict 中。 """ pass # 使用示例 d = CaseInsensitiveDict() d['Python'] = 'Fluent' print(d['python']) # 输出: 'Fluent' print(d['PYTHON']) # 输出: 'Fluent' print('PyThOn' in d) # 输出: True

对于以上示例,需要注意以下几点:

  • 每个方法最后都要使用super()!这是传球,混入类一定得把球传下去,不然调用就中断了。
  • 为什么不直接继承dict,而是继承UserDict,这是因为如果继承自 dict,那么像 d.update() 这种内置方法在 C 语言层面运行,会直接绕过你在 Mixin 中定义的 __setitem__。如果想深入了解可以查看这篇文章。
  • 混入类是一种可插拔的能力,如果你别的字典也需要key不区分大小写的功能,那么直接继承CaseInsensitiveMixin就可以!

对使用继承的几点建议

应该优先使用对象组合,而不是类继承。

这是一个老生常谈的问题,继承虽然强大,但它是程序中耦合度最高的关系。滥用继承可能会造成如下影响:

  1. 会造成方法污染,因为你一旦继承了,你就得被迫拥有父类的所有方法,即使你只需要一个方法。这点笔者在实际开发中深受其害。。。
  2. 继承层级一旦变深或变广,你的代码将会很难读懂,MRO会变得非常混乱,我们阅读代码的时候,很难一眼看出下一步到底会到
  3. 哪个类。
  4. 灵活性也会降低,一旦你声明了classA(B),那这个关系在程序运行前就定死了,我们无法在运行时改变他的继承关系。

继承并非洪水猛兽

既然继承有这么多危害,为什么还要用继承呢?其实不是继承有害,而是滥用继承的人有害。很多开发者错误地使用继承来获取代码复用。虽然组合优于继承,但是在一些情况中继承依然是最佳选择:

  1. 当你需要利用多态,确保子类必须实现某些 ABC(抽象基类)接口时。
  2. 上文讲到的混入类,对于一段通用且独立的逻辑可以使用混入类来作为一个插件。

什么时候该用继承?

可能其他文章会告诉只需要明确是什么(Is-A),还是有什么(Has-A)的关系就可以了。其实不然,在Is-A的条件下,还得满足:

  • 需要多态: 你希望把子类对象丢给一个只认识父类的函数。比如:draw_shape(shape) 函数能处理所有继承自 Shape 的子类。
  • 接口承诺: 你希望强制子类实现父类定义的某些方法(通常使用抽象基类 abc.ABC)。
  • 遵循 Liskov 替换原则 (LSP): 子类必须能完全替代父类而不会导致程序出错。如果子类需要“屏蔽”父类的某个功能(比如“鸵鸟”继承了“鸟”但要禁用 fly 方法),说明继承用错了。
http://www.jsqmd.com/news/463453/

相关文章:

  • NFTMarket 1 | NFT 简介、业务、技术方案
  • 四字节十六进制转化为单精度IEEE 754 浮点数
  • 打开软件就弹出vccorlib120.dll如何修复? 附免费下载方法分享
  • Ray + LanceDB + Daft 构建大规模向量数据分析管道
  • 计算机软件资格考试——专业英语
  • 没有 Base Code 谈何重构?揭秘智能零零AI论文助手从 0 到 1 的大模型结构化生成引擎
  • 打开软件就弹出vcomp.dll如何修复? 附免费下载方法分享
  • macbookair安装openclaw
  • Ray 集群多用户资源隔离实践
  • MySQL 进阶:库与表的DDL核心操作全指南(含实战案例)
  • 工业 + AI 落地实践:JBoltAI在工业场景的应用解析
  • 打卡信奥刷题(2938)用C++实现信奥题 P5800 [SEERC 2019] Life Transfer
  • 单片机高阻态:数字电路中的“隐形守护者”
  • Qt开发与MySQL数据库教程(一)——配置MySQL
  • 数据|非rag的类人检索
  • Java团队转型AI应用开发:挑战与JBoltAI的破局之道
  • 打卡信奥刷题(2939)用C++实现信奥题 P5810 [SCOI2004] 文本的输入
  • 化学绘图效率革命:InDraw五大核心功能全解析,从OCR识别到CAS号检索的实战指南
  • JBoltAI视频SOP:让“工业+AI”更高效直观
  • Python爬虫实战:监控贝壳找房小区均价与挂牌增量!
  • 物联网毕业设计效率提升指南:基于STM32原理图的模块化设计与快速验证方法
  • Spring Boot WebClient性能比RestTemplate高?看完秒懂!
  • 打卡信奥刷题(2940)用C++实现信奥题 P5815 [CQOI2010] 扑克牌
  • MTools教育应用:智能批改系统开发实战
  • 次元画室生成网络拓扑图:运维与网络教学的AI助手
  • 1.9 电子商城核心链路质量保障:从下单到支付的测试实战拆解
  • 使用IDEA开发RVC模型Java调用客户端:工程化配置与调试技巧
  • Leaflet与turf.js实战:动态生成等值线图并实现精准值交互展示
  • ArcGIS坐标系实战:从基础概念到投影变换全解析
  • Clawdbot汉化版企业微信实战:消息模板开发、事件回调处理、菜单集成