- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议58:理解 MRO 与多继承
跟其他编程语言一样,Python也支持多继承。多继承的语法非常简单。
class DerivedClassName(Base1, Base2, Base3)
谈到多继承,我们来讨论一下图6-3所示的菱形继承的经典问题。
图6-3 多继承UML示意图
假设有图6-3所示继承关系,当用古典类实现的时候,如果有实例d=D(),当调用d.getvalue()和d.show()方法的时候分别对应哪个父类中的方法?当改为新式类来实现时,结果又将是怎样的呢?我们来看具体实现:
class A(): def getvalue(self): print "return value of A" def show(self): print "I can show the information of A" class B(A): def getvalue(self): print "return value of B" class C(A): def getvalue(self): print "return value of C" def show(self): print "I can show the information of C" class D(B,C):pass
当用古典类实现的时候我们会发现,分别调用的是B类的getvalue()方法和A类中的show()方法,而当改为新式类实现(请读者自行验证)的时候,结果却变为调用B类的getvalue()方法和C类的show()方法。从两种不同实现方式的输出上也可以证实这一点。
古典类输出如下:
return value of B I can show the information of A
新式类输出如下:
return value of B I can show the information of C
为什么两种情况下输出结果会有所不同呢?这背后到底发生了什么?根本原因在哪里?实际上,导致这些不同点的根本原因在于古典类和新式类之间所采取的MRO(Method Resolution Order,方法解析顺序)的实现方式存在差异。
在古典类中,MRO搜索采用简单的自左至右的深度优先方法,即按照多继承申明的顺序形成继承树结构,自顶向下采用深度优先的搜索顺序,当找到所需要的属性或者方法的时候就停止搜索。因此如图6-3所示,当调用d.getvalue()的时候,其搜索顺序为D->B,所以调用的是B类中对应的方法。而d.show()的搜索顺序为D->B->A,因此最后调用的是A类中对应的方法。
而新式类采用的是C3 MRO搜索方法,该算法描述如下:
假定,C1C2...CN表示类C1到CN的序列,其中序列头部元素(head)=C1,序列尾部(tail)定义为=C2..CN;
C继承的基类自左向右分别表示为B1,B2...BN;
L[C]表示C的线性继承关系,其中L[object]=object。
算法具体过程如下:
L[C(B1 ... BN)] = C + merge(L[B1] ... L[BN], B1 ... BN)
其中merge方法的计算规则如下:在L[B1]...L[BN],B1...BN中,取L[B1]的head,如果该元素不在L[B2]...L[BN],B1...BN的尾部序列中,则添加该元素到C的线性继承序列中,同时将该元素从所有列表中删除(该头元素也叫good head),否则取L[B2]的head。继续相同的判断,直到整个列表为空或者没有办法找到任何符合要求的头元素(此时将引发一个异常)。
我们结合上面的例子来说明C3 MRO算法的具体计算方法,以新式类实现的上述菱形继承关系如图6-4所示。
根据算法规则有如下关系表达式:
L(O)=O ;L(A)=AO ;
则:
L(B)=B+merge(L(A))=B+merge(AO)=B+A+merge(O,O)=B,A,O L(C)=C+merge(L(A))=C+merge(AO)=C+A+merge(O,O)=C,A,O L(D)=D+merge(L(B),L(C),BC) =D+merge(BAO,CAO,BC) =D+B+merge(AO,CAO,C)( 下一个计算取AO 的头A ,但A 包含在CAO 的尾部,因此不满足条件, 跳到下一个元素CAO 继续计算) =D+B+C+merge(AO,AO) =D+B+C+A+O =DBCAO
因此对于上述例子,当D的实例d调用getvalue()和show()方法时按照D->B->C->A->O的顺序进行搜索,在第一个找个该方法的位置停止搜素并调用该类对应的方法,因此getvalue()会在B的类中找到对应的方法,而show会在C()类中找到对应的方法。
关于MRO的搜索顺序我们也可以在新式类中通过查看__mro__属性得到证实。D.__mro__的输出如下:
(<class'__main__.D'>,<class'__main__.B'>,<class'__main__.C'>,<class'__mai n__.A'>,<type'object'>)
实际上MRO虽然叫方法解析顺序,但它不仅是针对方法搜索,对于类中的数据属性也适用。读者可以自行验证。
根据C3 MRO算法的描述,如果找不到满足条件的good head,则会摒弃该元素从而对下一个元素进行查找。但如果找遍了所有的元素都找不到符合条件的good head会怎么样呢?来看一个具体例子。
class A(object): pass class B(object): pass class C(A, B): pass ... ... ... ... ①基类顺序为A ,B class D(B, A): pass ... ... ... ... ②基类顺序为B ,A class E(C, D): pass
运行程序我们会发现这种情况下有异常抛出。
TypeError: Error when calling the metaclass bases Cannot create a consistent method resolution order (MRO) for bases B, A
根据上述代码的继承关系图(请读者自行画出)和MRO算法可以得出:
L(E)=E+merge(L(C),L(D),CD) =E+merge(CABO,CBAO,CD) =E+C+merge(ABO,BAO,D) =E+C+D+merge(ABO+BAO)
当算法进行到最后一步的时候便再也找不到满足条件的head了,因为当选择ABO的头A元素的时候,发现其包含在BAO的尾部AO中;同理,B包含在BO中,此时便形成了一个死锁,Python解释器此时不知道如何处理这种情况,便直接抛出异常,这就是上述例子有异常抛出的原因。
菱形继承是我们在多继承设计的时候需要尽量避免的一个问题。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论