设为首页 收藏本站
查看: 832|回复: 0

[经验分享] 深入super,看Python如何解决钻石继承难题

[复制链接]

尚未签到

发表于 2015-11-29 11:53:09 | 显示全部楼层 |阅读模式
1.   Python的继承以及调用父类成员
  python子类调用父类成员有2种方法,分别是普通方法和super方法
  假设Base是基类



class Base(object):
  def __init__(self):
    print “Base init”
  则普通方法如下



class Leaf(Base):
def __init__(self):
Base.__init__(self)
print “Leaf init”
  super方法如下



class Leaf(Base):
def __init__(self):
super(Leaf, self).__init__()
print “Leaf init”
  在上面的简单场景下,两种方法的效果一致:
  >>> leaf = Leaf()
  Base init
  Leaf init

2.   钻石继承遇到的难题
  当我们来到钻石继承场景时,我们就遇到了一个难题:
DSC0000.png
  如果我们还是使用普通方法调用父类成员,代码如下:



class Base(object):
def __init__(self):
print “Base init”
class Medium1(Base):
def __init__(self):
Base.__init__(self)
print “Medium1 init”
class Medium2(Base):
def __init__(self):
Base.__init__(self)
print “Medium2 init”
class Leaf(Medium1, Medium2):
def __init__(self):
Medium1.__init__(self)
Medium2.__init__(self)
print “Leaf init”   
  当我们生成Leaf对象时,结果如下:
  >>> leaf = Leaf()
  Base init
  Medium1 init
  Base init
  Medium2 init
  Leaf init
  
  可以看到Base被初始化了两次!这是由于Medium1和Medium2各自调用了Base的初始化函数导致的。

3.   各语言的解决方法
  钻石继承中,父类被多次初始化是个非常难缠的问题,我们来看看其他各个语言是如何解决这个问题的:

3.1. C++
  C++使用虚拟继承来解决钻石继承问题。
  Medium1和Medium2虚拟继承Base。当生成Leaf对象时,Medium1和Medium2并不会自动调用虚拟基类Base的构造函数,而需要由Leaf的构造函数显式调用Base的构造函数。

3.2. Java
  Java禁止使用多继承。
  Java使用单继承+接口实现的方式来替代多继承,避免了钻石继承产生的各种问题。

3.3. Ruby
  Ruby禁止使用多继承。
  Ruby和Java一样只支持单继承,但它对多继承的替代方式和Java不同。Ruby使用Mixin的方式来替代,在当前类中mixin入其他模块,来做到代码的组装效果。

3.4. Python
  Python和C++一样,支持多继承的语法。但Python的解决思路和C++完全不一样,Python使用的是super
  我们把第2章的钻石继承用super重写一下,看一下输出结果



class Base(object):
def __init__(self):
print “Base init”
class Medium1(Base):
def __init__(self):
super(Medium1, self).__init__()
print “Medium1 init”
class Medium2(Base):
def __init__(self):
super(Medium2, self).__init__()
print “Medium2 init”
class Leaf(Medium1, Medium2):
def __init__(self):
super(Leaf, self).__init__()
print “Leaf init”        
  我们生成Leaf对象:
  >>> leaf = Leaf()
  Base init
  Medium2 init
  Medium1 init
  Leaf init
  可以看到整个初始化过程符合我们的预期,Base只被初始化了1次。而且重要的是,相比原来的普通写法,super方法并没有写额外的代码,也没有引入额外的概念

4.   super的内核:mro
  要理解super的原理,就要先了解mro。mro是method resolution order的缩写,表示了类继承体系中的成员解析顺序。
  
  在python中,每个类都有一个mro的类方法。我们来看一下钻石继承中,Leaf类的mro是什么样子的:
  >>> Leaf.mro()
  [<class '__main__.Leaf'>, <class '__main__.Medium1'>, <class '__main__.Medium2'>, <class '__main__.Base'>, <type 'object'>]
  
  可以看到mro方法返回的是一个祖先类的列表。Leaf的每个祖先都在其中出现一次,这也是super在父类中查找成员的顺序。
  通过mro,python巧妙地将多继承的图结构,转变为list的顺序结构。super在继承体系中向上的查找过程,变成了在mro中向右的线性查找过程,任何类都只会被处理一次。
  
  通过这个方法,python解决了多继承中的2大难题:
  1. 查找顺序问题。从Leaf的mro顺序可以看出,如果Leaf类通过super来访问父类成员,那么Medium1的成员会在Medium2之前被首先访问到。如果Medium1和Medium2都没有找到,最后再到Base中查找。
  2. 钻石继承的多次初始化问题。在mro的list中,Base类只出现了一次。事实上任何类都只会在mro list中出现一次。这就确保了super向上调用的过程中,任何祖先类的方法都只会被执行一次。
  
  至于mro的生成算法,可以参考这篇wiki:https://en.wikipedia.org/wiki/C3_linearization

5.   super的具体用法
  我们首先来看一下python中的super文档
  >>> help(super)
  Help on class super in module __builtin__:
  class super(object)
  |  super(type, obj) -> bound super object; requires isinstance(obj, type)
  |  super(type) -> unbound super object
  |  super(type, type2) -> bound super object; requires issubclass(type2, type)
  
  光从字面来看,这可以算是python中最语焉不详的帮助文档之一了。甚至里面还有一些术语误用。那super究竟应该怎么用呢,我们重点来看super中的第1和第3种用法

5.1. super(type, obj)
  当我们在Leaf的__init__中写这样的super时:



class Leaf(Medium1, Medium2):
def __init__(self):
super(Leaf, self).__init__()
print “Leaf init”
  super(Leaf, self).__init__()的意思是说:


  • 获取self所属类的mro, 也就是[Leaf, Medium1, Medium2, Base]
  • 从mro中Leaf右边的一个类开始,依次寻找__init__函数。这里是从Medium1开始寻找
  • 一旦找到,就把找到的__init__函数绑定到self对象,并返回
  
  从这个执行流程可以看到,如果我们不想调用Medium1的__init__,而想要调用Medium2的__init__,那么super应该写成:super(Medium1, self)__init__()

5.2. super(type, type2)
  当我们在Leaf中写类方法的super时:



class Leaf(Medium1, Medium2):
def __new__(cls):
obj = super(Leaf, cls).__new__(cls)
print “Leaf new”
return obj
  super(Leaf, cls).__new__(cls)的意思是说:


  • 获取cls这个类的mro,这里也是[Leaf, Medium1, Medium2, Base]
  • 从mro中Leaf右边的一个类开始,依次寻找__new__函数
  • 一旦找到,就返回“非绑定”的__new__函数
  
  由于返回的是非绑定的函数对象,因此调用时不能省略函数的第一个参数。这也是这里调用__new__时,需要传入参数cls的原因
  同样的,如果我们想从某个mro的某个位置开始查找,只需要修改super的第一个参数就行

6.   小结
  至此,我们讲解了和super相关的用法及原理,小结一下我们讲过的内容有:


  • python调用父类成员共有2种方法:普通方法,super方法
  • 在钻石继承中,普通方法会遇到Base类两次初始化的问题
  • 简述了其他语言对这个问题的解决方法,并用实例展示了python使用super可以解决此问题
  • 在讲super具体用法前,先讲了super的内核:mro的知识和原理
  • 讲解了super两种主要的用法及原理
  
  标签:python, super, mro, 多继承

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-144889-1-1.html 上篇帖子: 用python调用C的动态链接库 下篇帖子: 2015/11/3用Python写游戏,pygame入门(3):字体模块、事件显示和错误处理
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表