资讯专栏INFORMATION COLUMN

Python类继承的高级特性

Yumenokanata / 2620人阅读

摘要:昨天在类的多重继承那里纠结了好久在提问版块提了个问题探讨了探讨链接才完全搞明白现在把类的特性整理下供以后参考正文首先得说明的是的类分为经典类和新式类经典类是之前的东西但是在还在兼容但是在之后的版本就只承认新式类了新式类在之后的版本中都可以使

昨天在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高级编程》

http://www.cnblogs.com/lovemo1314/archive/2011/05/03/2035005.html

http://www.cnblogs.com/i2u9/archive/2013/03/19/pythonmroc3.html

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/37436.html

相关文章

  • SegmentFault 技术周刊 Vol.40 - 2018,来学习一门新编程语言吧!

    摘要:入门,第一个这是一门很新的语言,年前后正式公布,算起来是比较年轻的编程语言了,更重要的是它是面向程序员的函数式编程语言,它的代码运行在之上。它通过编辑类工具,带来了先进的编辑体验,增强了语言服务。 showImg(https://segmentfault.com/img/bV1xdq?w=900&h=385); 新的一年不知不觉已经到来了,总结过去的 2017,相信小伙们一定有很多收获...

    caspar 评论0 收藏0
  • SegmentFault 技术周刊 Vol.40 - 2018,来学习一门新编程语言吧!

    摘要:入门,第一个这是一门很新的语言,年前后正式公布,算起来是比较年轻的编程语言了,更重要的是它是面向程序员的函数式编程语言,它的代码运行在之上。它通过编辑类工具,带来了先进的编辑体验,增强了语言服务。 showImg(https://segmentfault.com/img/bV1xdq?w=900&h=385); 新的一年不知不觉已经到来了,总结过去的 2017,相信小伙们一定有很多收获...

    nihao 评论0 收藏0
  • SegmentFault 技术周刊 Vol.40 - 2018,来学习一门新编程语言吧!

    摘要:入门,第一个这是一门很新的语言,年前后正式公布,算起来是比较年轻的编程语言了,更重要的是它是面向程序员的函数式编程语言,它的代码运行在之上。它通过编辑类工具,带来了先进的编辑体验,增强了语言服务。 showImg(https://segmentfault.com/img/bV1xdq?w=900&h=385); 新的一年不知不觉已经到来了,总结过去的 2017,相信小伙们一定有很多收获...

    Drummor 评论0 收藏0
  • php学习笔记(三)面向对象高级实践

    摘要:由于静态方法不需要通过对象即可调用,所以伪变量在静态方法中不可用。继承一个抽象类的时候,子类必须定义父类中的所有抽象方法另外,这些方法的访问控制必须和父类中一样。 extends对象继承 PHP中类不允许同时继承多个父类,也就是extends后面只能跟一个父类名称,这个特性被称为PHP的单继承特性 当扩展一个类,子类就会继承父类所有公有的和受保护的方法。除非子类覆盖了父类的方法,被...

    Hancock_Xu 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<