深入super,看Python如何解决钻石继承难题

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.   钻石继承遇到的难题

当我们来到钻石继承场景时,我们就遇到了一个难题:

如果我们还是使用普通方法调用父类成员,代码如下:

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__()的意思是说:

  1. 获取self所属类的mro, 也就是[Leaf, Medium1, Medium2, Base]
  2. 从mro中Leaf右边的一个类开始,依次寻找__init__函数。这里是从Medium1开始寻找
  3. 一旦找到,就把找到的__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)的意思是说:

  1. 获取cls这个类的mro,这里也是[Leaf, Medium1, Medium2, Base]
  2. 从mro中Leaf右边的一个类开始,依次寻找__new__函数
  3. 一旦找到,就返回“非绑定”的__new__函数

由于返回的是非绑定的函数对象,因此调用时不能省略函数的第一个参数。这也是这里调用__new__时,需要传入参数cls的原因

同样的,如果我们想从某个mro的某个位置开始查找,只需要修改super的第一个参数就行

6.   小结

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

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

标签:python, super, mro, 多继承

时间: 2024-08-27 15:47:56

深入super,看Python如何解决钻石继承难题的相关文章

(转载)深入super,看Python如何解决钻石继承难题

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(Bas

Python Special Syntax 7:继承

继续在上一节的类的基础上测试继承: #-*-coding:utf-8 import Syntax2 class Student(Syntax2.Person): def __init__(self,name,age): Syntax2.Person.__init__(self,name) Syntax2.Person.printName(self) print('age %s' % age) self.age=age def detail(self): print('Name %s, age:%

python作用域和多继承

python作用域 python无块级作用域 看c语言代码: #include<stdio.h> int main() { if(2 > 0) { int i = 0; } printf("i = %d", i); return 0; } 在这段代码中,if子句引入了一个局部作用域,变量i就存在于这个局部作用域中,但对外不可见,因此,接下来在printf函数中对变量i的引用会引发编译错误,但是在python中并非如此 看下面代码: if 1 == 1: name =

python错误解决:SyntaxError: Non-ASCII character &#39;\xd3&#39; in file crawler.py

我写的python代码中遇到编码问题:SyntaxError: Non-ASCII character '\xd3' in file crawler.py 原因:代码中有需要输出中文的部分,但是运行时出现了这个错误: 错误中提示看这个链接:http://www.python.org/peps/pep-0263.html 解决问题的方法: 如果在python中出现了非ASCII码以外的其他字符,需要在代码的开头声明字符格式 解决之一: 在程序的开头加上#-*-coding:utf-8-*- ~te

python学习之类的继承

面向对象中一个重要的特性就是继承,继承的好处就是提高代码的重用率,减少不必要的代码.继承是父类与子类的关系,当子类继承了父类后,就具有了父类的所有变量和方法.在python中定义继承的语法是:class 派生类名(基类名).在使用python的继承时需要注意一下几点: (1)当父类定义了__init__()初始化方法时,子类不会自动调用,而需要我们显示调用,如果我们要扩展父类的变量,可以在__init__()添加参数. (2)在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量

要继续看Python写算法的内容请到那里去

因为在这里发文章的时候,莫名其妙的出现发布出去的问题,客服告知是因为链接或者敏感词. 能不能告诉我哪里出了问题?我可以修改,以便再发. 但是,没有人告诉我,只是告诉我不能发. 另外,能不能公布一下敏感词?以后我在遣词造句的时候,才可以避免. 但是,没有看到敏感词列表. 以后我的文章将发布在https://www.github.com/qiwsir/algorithm里面,有兴趣的可以到那里阅读. 要继续看Python写算法的内容请到那里去,布布扣,bubuko.com

Android开发华为手机无法看log日志解决方法

Android开发华为手机无法看log日志解决方法 上班的时候,由于开发工具由Eclipse改成Android Studio后,原本的华为手机突然无法查看崩溃日志了,大家都知道,若是无法查看日志要它毛用啊? 刚开始没想过是手机问题,毕竟在Eclipse中是完好了,结果在AS中华为了大量时间查找原因,最后,偶然换个手机发现别的手机正常... 最后百度发现解决方法: 进入拨号界面输入:*#*#2846579#*#* 依次选择[工程菜单 —> 后台设置 —> LOG设置 —> LOG开关]  

python中类的多继承的搜索算法

1.Python的类可以继承多个类,Java和C#中则只能继承一个类 2.Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先和广度优先 当类是经典类时,多继承情况下,会按照深度优先方式查找 当类是新式类时,多继承情况下,会按照广度优先方式查找 经典类和新式类,从字面上可以看出一个老一个新,新的必然包含了跟多的功能,也是之后推荐的写法,从写法上区分的话,如果 当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类.   class D: def bar(

摆拍韦节宾:Testin云测雪中送炭般解决开发者测试难题

摆拍韦节宾:Testin云测雪中送炭般解决开发者测试难题 2014/11/26 · Testin · 开发者访谈 定格动画是通过逐格地拍摄对象然后使之连续放映,从而产生仿佛活了一般的人物或你能想象到的任何奇异角色,一般都是由黏土偶,木偶或混合材料的角色来演出的.这种动画形式的历史和传统意义上的手绘动画历史一样长,甚至可能更古老.由于定格动画制造出的人物富有鲜明的个性,故事情节丰富,且易于操作,而受到年轻人越来越多的追捧. "摆拍"就是一款主打定格动画的APP,用来拍摄定格动画的软件,并