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

 更新时间:2014年11月09日 11:40:31   投稿:hebedich  
昨天在Python类的多重继承那里纠结了好久,咨询了不少高手之后,才完全搞明白,现在把类的特性整理下,供以后参考,也给有需要的小伙伴们参考下

正文

首先得说明的是,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保存dict字典类型数据到Mysql并自动创建表与列

    Python保存dict字典类型数据到Mysql并自动创建表与列

    这篇文章主要介绍了Python保存dict字典类型数据到Mysql并自动创建表与列,字典是另一种可变容器模型,且可存储任意类型对象,想了解更多内容的小伙伴可以和小编一起进入下面文章学习更多内容,希望对你有所帮助
    2022-02-02
  • pytorch中关于distributedsampler函数的使用

    pytorch中关于distributedsampler函数的使用

    这篇文章主要介绍了pytorch中关于distributedsampler函数的使用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • Python谱减法语音降噪实例

    Python谱减法语音降噪实例

    今天小编就为大家分享一篇Python谱减法语音降噪实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-12-12
  • python中的单引号双引号区别知识点总结

    python中的单引号双引号区别知识点总结

    在本篇文章中小编给大家整理了关于python中的单引号双引号有什么区别的相关知识点以及实例代码,需要的朋友们参考下。
    2019-06-06
  • 解决Pycharm下面出现No R interpreter defined的问题

    解决Pycharm下面出现No R interpreter defined的问题

    今天小编就为大家分享一篇解决Pycharm下面出现No R interpreter defined的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-10-10
  • Python实现计算AUC的示例代码

    Python实现计算AUC的示例代码

    AUC(Area under curve)是机器学习常用的二分类评测手段,直接含义是ROC曲线下的面积。本文将利用Python语言实现计算AUC,感兴趣的可以学习一下
    2022-07-07
  • Python3如何在Windows和Linux上打包

    Python3如何在Windows和Linux上打包

    这篇文章主要介绍了Python3如何在Windows和Linux上打包,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • Python中re模块下的函数详解

    Python中re模块下的函数详解

    这篇文章主要介绍了Python中re模块下的函数详解,re.match() 函数是从头开始匹配一个符合规则的字符串,从起始位置开始匹配,匹配成功返回一个对象,未匹配成功返回None,需要的朋友可以参考下
    2023-08-08
  • Python面向对象程序设计示例小结

    Python面向对象程序设计示例小结

    这篇文章主要介绍了Python面向对象程序设计,结合实例形式总结分析了Python面向对象程序设计中比较常见的类定义、实例化、继承、私有变量等相关使用技巧与操作注意事项,需要的朋友可以参考下
    2019-01-01
  • Python操作PDF文档的主流库使用指南

    Python操作PDF文档的主流库使用指南

    PDF因其跨平台、格式固定的特性成为文档交换的标准,然而,由于其复杂的内部结构,程序化操作 PDF 一直是个挑战,本文主要为大家整理了Python操作PDF的三大主流库的使用,希望对大家有所帮助
    2025-07-07

最新评论