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中的yield浅析
Jun 16 Python
在 Django/Flask 开发服务器上使用 HTTPS
Jul 03 Python
python3 发送任意文件邮件的实例
Jan 23 Python
Python Tornado批量上传图片并显示功能
Mar 26 Python
Python无头爬虫下载文件的实现
Apr 02 Python
python如何判断IP地址合法性
Apr 05 Python
ffmpeg+Python实现B站MP4格式音频与视频的合并示例代码
Oct 21 Python
Python爬虫模拟登陆哔哩哔哩(bilibili)并突破点选验证码功能
Dec 21 Python
Python中生成ndarray实例讲解
Feb 22 Python
TensorFlow的自动求导原理分析
May 26 Python
用Python将GIF动图分解成多张静态图片
Jun 11 Python
python unittest单元测试的步骤分析
Aug 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
Drupal7连接多个数据库及常见问题解决
2014/03/02 PHP
Smarty局部缓存的几种方法简介
2014/06/17 PHP
PHP动态柱状图实现方法
2015/03/30 PHP
ThinkPHP数据操作方法总结
2015/09/28 PHP
全新Mac配置PHP开发环境教程
2016/02/03 PHP
PHP基于Closure类创建匿名函数的方法详解
2017/08/17 PHP
List the Codec Files on a Computer
2007/06/11 Javascript
uploadify 3.0 详细使用说明
2012/06/18 Javascript
js实现的tab标签切换效果代码分享
2015/08/25 Javascript
jQuery动态加载css文件实现方法
2016/06/15 Javascript
jQuery.ajax实现根据不同的Content-Type做出不同的响应
2016/11/03 Javascript
微信小程序 小程序制作及动画(animation样式)详解
2017/01/06 Javascript
js利用for in循环获取 一个对象的所有属性以及值的实例
2017/03/30 Javascript
jquery+ajaxform+springboot控件实现数据更新功能
2018/01/22 jQuery
微信小程序实现topBar底部选择栏效果
2018/07/20 Javascript
微信小程序网络请求封装示例
2018/07/24 Javascript
小程序云开发初探(小结)
2018/10/24 Javascript
微信小程序实现预览图片功能
2020/10/22 Javascript
vue设置导航栏、侧边栏为公共页面的例子
2019/11/01 Javascript
使用vue-cli4.0快速搭建一个项目的方法步骤
2019/12/04 Javascript
node.js事件轮询机制原理知识点
2019/12/22 Javascript
[02:42]DOTA2城市挑战赛收官在即 四强之争风起云涌
2018/06/05 DOTA
通过Python使用saltstack生成服务器资产清单
2016/03/01 Python
Python使用SQLite和Excel操作进行数据分析
2018/01/20 Python
Python实现点云投影到平面显示
2020/01/18 Python
python 字符串的驻留机制及优缺点
2020/06/19 Python
Python return语句如何实现结果返回调用
2020/10/15 Python
Python并发爬虫常用实现方法解析
2020/11/19 Python
Pandas直接读取sql脚本的方法
2021/01/21 Python
HTML5 Canvas绘制五星红旗
2016/05/04 HTML / CSS
英国最大的女士服装零售商:Bonmarché
2017/08/17 全球购物
戴森香港官方网站:Dyson香港
2021/02/11 全球购物
PHP中如何创建和修改数组
2012/05/02 面试题
大学毕业生自我鉴定
2013/11/05 职场文书
汽车技术服务英文求职信范文
2014/01/02 职场文书
SpringBoot+VUE实现数据表格的实战
2021/08/02 Java/Android