Python类的多重继承问题深入分析


Posted in Python onNovember 09, 2014

正文

首先得说明的是,Python的类分为经典类 和 新式类
经典类是python2.2之前的东西,但是在2.7还在兼容,但是在3之后的版本就只承认新式类了
新式类在python2.2之后的版本中都可以使用

经典类和新式类的区别在于:

经典类是默认没有派生自某个基类的,而新式类是默认派生自object这个基类的:

# old style

class A():pass
# new style

class A(obejct):pass

2.经典类在类多重继承的时候是采用从左到右深度优先原则匹配方法的..而新式类是采用C3算法(不同于广度优先)进行匹配的

3.经典类是没有__MRO__和instance.mro()调用的,而新式类是有的.

为什么不用经典类,要更换到新式类

因为在经典类中的多重继承会有些问题...可能导致在继承树中的方法查询绕过后面的父类:

class A():

    def foo1(self):

        print "A"

class B(A):

    def foo2(self):

        pass

class C(A):

    def foo1(self):

        print "C"

class D(B, C):

    pass
d = D()

d.foo1()

按照经典类的查找顺序从左到右深度优先的规则,在访问d.foo1()的时候,D这个类是没有的..那么往上查找,先找到B,里面没有,深度优先,访问A,找到了foo1(),所以这时候调用的是A的foo1(),从而导致C重写的foo1()被绕过.

所以python引入了新式类的概念,每个基类都继承自object并且,他的匹配规则也从深度优先换到了C3

C3算法

C3算法是怎么做匹配的呢..在问答版块上面讨论之后,归结如下:

C3算法的一个核心是merge.

在merge列表中,如果第一个序列mro的第一个类是出现在其它序列,并且也是第一个,或者不出现其它序列,那么这个类就会从这些序列中删除,并合到访问顺序列表中
比如:(引用问题中zhuangzebo的回答@zhuangzebo)

class A(O):pass

class B(O):pass

class C(O):pass

class D(A,B):pass

class E(C,D):pass

首先需要知道 O(object)的mro(method resolution order)列表是[O,]
那么接下来是:

mro(A) = [A, O]

mro(B) = [B, O]

mro(C) = [C, O]

mro(D) = [D] + merge(mro(A), mro(B), [A, B])

= [D] + merge([A, O], [B, O], [A, B])

= [D, A] + merge([O], [B, O], [B])

= [D, A, B] + merge([O], [O])

= [D, A, B, O]

mro(E) = [E] + merge(mro(C), mro(D), [C, D])

= [E] + merge([C, O], [D, A, B, O], [C, D])

= [E, C] + merge([O], [D, A, B, O], [D])

= [E, C, D] + merge([O], [A, B, O])

= [E, C, D, A, B] + merge([O], [O])

= [E, C, D, A, B, O]

然后还有一种特殊情况:
比如:
merge(DO,CO,C) 先merge的是D
merge(DO,CO,C) 先merge的是C
意思就是.当出现有 一个类出现在两个序列的头(比如C) 这种情况和 这个类只有在一个序列的头(比如D) 这种情况同时出现的时候,按照顺序方式匹配。

新式类生成的访问序列被存储在一个叫MRO的只读列表中..
你可以使用instance.__MRO__或者instance.mro()来访问

最后匹配的时候就按照MRO序列的顺序去匹配了

C3和广度优先的区别:

举个例子就完全明白了:

class A(object):pass

class B(A):pass

class C(B):pass

class D(A):pass

class E(D):pass

class F(C, E):pass

按照广度优先遍历,F的MRO序列应该是[F,C,E,B,D,A]
但是C3是[F,E,D,C,B,A]
意思是你可以当做C3是在一条链路上深度遍历到和另外一条链路的交叉点,然后去深度遍历另外一条链路,最后遍历交叉点

新式类和经典类的super和按类名访问问题

在经典类中,你如果要访问父类的话,是用类名来访问的..

class A():

    def __init__(self):

        print "A"

class B(A):

    def __init__(self):

        print "B"

        A.__init__(self)  #python不会默认调用父类的初始化函数的

这样子看起来没三问题,但是如果类的继承结构比较复杂,会导致代码的可维护性很差..
所以新式类推出了super这个东西...

class A():

    def __init__(self):

        print "A"

class B(A):

    def __init__(self):

        print "B"

        super(B,self).__init__()

这时候,又有一个问题:当类是多重继承的时候,super访问的是哪一个类呢?
super实际上是通过__MRO__序列来确定访问哪一个类的...实际上就是调用__MRO__中此类后面的一个类的方法.
比如序列为[F,E,D,C,B,A]那么F中的super就是E,E的就是D

super和按照类名访问 混合使用带来的坑

class A(object):

  def __init__(self):

   print "enter A"

   print "leave A"
 class B(object):

  def __init__(self):

   print "enter B"

   print "leave B"
 class C(A):

  def __init__(self):

   print "enter C"

   super(C, self).__init__()

   print "leave C"
 class D(A):

  def __init__(self):

   print "enter D"

   super(D, self).__init__()

   print "leave D"

 class E(B, C):

  def __init__(self):

   print "enter E"

   B.__init__(self)

   C.__init__(self)

   print "leave E"
 class F(E, D):

  def __init__(self):

   print "enter F"

   E.__init__(self)

   D.__init__(self)

   print "leave F"

这时候打印出来是:

 enter F

 enter E

 enter B

 leave B

 enter C

 enter D

 enter A

 leave A

 leave D

 leave C

 leave E

 enter D

 enter A

 leave A

 leave D

 leave F

可以看出来D和A的初始化函数被乱入了两次!
按类名访问就相当于C语言之前的GOTO语句...乱跳,然后再用super按顺序访问..就有问题了

所以建议就是要么一直用super,要么一直用按照类名访问

最佳实现:

避免多重继承
super使用一致
不要混用经典类和新式类
调用父类的时候注意检查类层次

以上便是本人对于python类的继承的认识了,希望对大家能有所帮助

Python 相关文章推荐
Python中的XML库4Suite Server的介绍
Apr 14 Python
实例说明Python中比较运算符的使用
May 13 Python
Python中的with...as用法介绍
May 28 Python
Python使用面向对象方式创建线程实现12306售票系统
Dec 24 Python
Python 实现一个颜色色值转换的小工具
Dec 06 Python
在python2.7中用numpy.reshape 对图像进行切割的方法
Dec 05 Python
Python 加密与解密小结
Dec 06 Python
windows下搭建python scrapy爬虫框架步骤
Dec 23 Python
详解Appium+Python之生成html测试报告
Jan 04 Python
深入解析Python小白学习【操作列表】
Mar 23 Python
python脚本之一键移动自定格式文件方法实例
Sep 02 Python
新版Pycharm中Matplotlib不会弹出独立的显示窗口的问题
Jun 02 Python
python查询mysql中文乱码问题
Nov 09 #Python
python刷投票的脚本实现代码
Nov 08 #Python
Django静态资源URL STATIC_ROOT的配置方法
Nov 08 #Python
Python中的__new__与__init__魔术方法理解笔记
Nov 08 #Python
Python使用百度API上传文件到百度网盘代码分享
Nov 08 #Python
python中readline判断文件读取结束的方法
Nov 08 #Python
Python实现基于HTTP文件传输实例
Nov 08 #Python
You might like
PHP中判断变量为空的几种方法分享
2013/08/26 PHP
ASP和PHP实现生成网站快捷方式并下载到桌面的方法
2014/05/08 PHP
PHP中抽象类和抽象方法概念与用法分析
2016/05/24 PHP
PHP自定义递归函数实现数组转JSON功能【支持GBK编码】
2018/07/17 PHP
键盘 keycode的值 javascript时触发事件时很有用的要素
2009/11/02 Javascript
用Jquery实现可编辑表格并用AJAX提交到服务器修改数据
2009/12/27 Javascript
js或css文件后面跟参数的原因说明
2010/01/09 Javascript
js中if语句的几种优化代码写法
2011/03/12 Javascript
jquery实现图片等比例缩放以及max-width在ie中不兼容解决
2013/03/21 Javascript
jQuery中removeAttr()方法用法实例
2015/01/05 Javascript
jQuery实现购物车数字加减效果
2015/03/14 Javascript
网页从弹窗页面单选框传值至父页面代码分享
2015/09/29 Javascript
非常漂亮的相册集 使用jquery制作相册集
2016/04/28 Javascript
jQuery中 $ 符号的冲突问题及解决方案
2016/11/04 Javascript
详解照片瀑布流效果(js,jquery分别实现与知识点总结)
2017/01/01 Javascript
JS html时钟制作代码分享
2017/03/03 Javascript
vue使用drag与drop实现拖拽的示例代码
2017/09/07 Javascript
iView-admin 动态路由问题的解决方法
2018/10/03 Javascript
使用easyui从servlet传递json数据到前端页面的两种方法
2019/09/05 Javascript
对layui中的onevent 和event的使用详解
2019/09/06 Javascript
解决Layui中templet中a的onclick参数传递的问题
2019/09/20 Javascript
基于JavaScript实现猜数字游戏代码实例
2020/07/30 Javascript
在Python的Flask框架中验证注册用户的Email的方法
2015/09/02 Python
python pandas dataframe 行列选择,切片操作方法
2018/04/10 Python
python实现扫描ip地址的小程序
2019/04/16 Python
Python实现多线程/多进程的TCP服务器
2019/09/03 Python
Python调用飞书发送消息的示例
2020/11/10 Python
IE滤镜与CSS3效果(详细整理分享)
2013/01/25 HTML / CSS
魅力惠奢品线上平台:MEI.COM
2016/11/29 全球购物
Java中会存在内存泄漏吗,请简单描述
2016/12/22 面试题
J2EE中的容器都包括哪些
2013/08/21 面试题
大班上学期幼儿评语
2014/04/30 职场文书
大学生国家助学金感谢信
2015/01/23 职场文书
三国演义读书笔记
2015/06/25 职场文书
Mysql 如何查询时间段交集
2021/06/08 MySQL
SpringBoot项目中控制台日志的保存配置操作
2021/06/18 Java/Android