深入super,看Python如何解决钻石继承难题 【转】
原文地址 http://www.cnblogs.com/testview/p/4651198.html
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__()的意思是说:
- 获取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两种主要的用法及原理
深入super,看Python如何解决钻石继承难题 【转】的更多相关文章
- 深入super,看Python如何解决钻石继承难题
1. Python的继承以及调用父类成员 python子类调用父类成员有2种方法,分别是普通方法和super方法 假设Base是基类 class Base(object): def __init_ ...
- (转载)深入super,看Python如何解决钻石继承难题
1. Python的继承以及调用父类成员 python子类调用父类成员有2种方法,分别是普通方法和super方法 假设Base是基类 class Base(object): def __init_ ...
- python之路----钻石继承
钻石继承 继承顺序 class A(object): def test(self): print('from A') class B(A): def test(self): print('from B ...
- python 全栈开发,Day20(object类,继承与派生,super方法,钻石继承)
先来讲一个例子 老师有生日,怎么组合呢? class Birthday: # 生日 def __init__(self,year,month,day): self.year = year self.m ...
- C++_day8_ 多重继承、钻石继承和虚继承
1.继承的复习 1.1 类型转换 编译器认为访问范围缩小是安全的. 1.2 子类的构造与析构 子类中对基类构造函数初始化只能写在初始化表里,不能写在函数体中. 阻断继承. 1.3 子类的拷贝构造与拷贝 ...
- C++:钻石继承与虚继承
QUESTION:什么是钻石继承? ANSWER:假设我们已经有了两个类Father1和Father2,他们都是类GrandFather的子类.现在又有一个新类Son,这个新类通过多继承机制对类Fat ...
- day25 python学习 继承,钻石继承 多态
---恢复内容开始--- 通过一个列子认识父类和子类中,子类的如何实现对父类默认属性调用,同时拥有自己的属性,如何在子类中调用父类的方法,class Ainmal: country='afdas' d ...
- python开发面向对象基础:接口类&抽象类&多态&钻石继承
一,接口类 继承有两种用途: 一:继承基类的方法,并且做出自己的改变或者扩展(代码重用) 二:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实 ...
- 4-13 object类,继承和派生( super) ,钻石继承方法
1,object 类 object class A: ''' 这是一个类 ''' pass a = A() print(A.__dict__) # 双下方法 魔术方法 创建一个空对象 调用init方法 ...
随机推荐
- java--连接SQL数据库获取验证码
1.导入SQL相关的包: 可以下载:mysql-connector-java-5.1.39-bin.jar 将包导入到工程的方法:project(在工程名上点鼠标右键) -> Build Pat ...
- MVC 导出Excel 的其中一方法(View导出excel)
场景:mvc下导出excel 思路:使用View导出excel 步骤: 1.导出标签添加事件 $("#export_A").click(function(){ //省略代码.... ...
- - >code vs 1475 m进制转十进制
1475 m进制转十进制 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 白银 Silver 题解 查看运行结果 题目描述 Description 将m进制数n转化成一个 ...
- WinForm BaseClass类常用通用方法
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.D ...
- 在jquery中,全选/全不选的表示方法
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/ ...
- ORACLE 迁移MYSQL 随笔
1.把服务器上的ORALCE 数据库导成DMP,然后导入本机的临时库 2.先把ORACLE 表中的DATE 改为TIMESTAMP; a.先建立个表 create table type_table_i ...
- C语言、结构体 定义
C语言允许用户自己建立由 不同类型数据组成的组合型数据结构 成为结构体. struct Student { int num; //学号 ]; //姓名为字符串 char sex; //性别为字符型 i ...
- Node的express框架安装
第一步:在cmd命令行下执行npm install -g express,安装全局的express. 第二步:在命令行中输入express,如果出现express不是内部命令时, 输入npm inst ...
- sql中NVARCHAR(MAX) 性能和占空间分析 varchar(n),nvarchar(n) 长度性能及所占空间分析
varchar(n),nvarchar(n) 中的n怎么解释: nvarchar(n)最多能存n个字符,不区分中英文. varchar(n)最多能存n个字节,一个中文是两个字节. 所占空间: nvar ...
- 整理:深度学习 vs 机器学习 vs 模式识别
http://www.csdn.net/article/2015-03-24/2824301 近200篇机器学习&深度学习资料分享(含各种文档,视频,源码等) http://developer ...