1
2
3
4
5
6
7
8
9
10
class A(object):    # A must be new-style class
   def __init__(self):
    print "enter A"
    print "leave A"
   
class B(C):     # A --> C
   def __init__(self):
    print "enter B"
    super(B, self).__init__()
    print "leave B"
在我们的印象中,对于super(B, self).__init__()是这样理解的:super(B, self)首先找到B的父类(就是类A),然后把类B的对象self转换为类A的对象,然后“被转换”的类A对象调用自己的__init__函数。

有一天某同事设计了一个相对复杂的类体系结构(我们先不要管这个类体系设计得是否合理,仅把这个例子作为一个题目来研究就好),代码如下

代码段4:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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"

f = 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

明显地,类A和类D的初始化函数被重复调用了2次,这并不是我们所期望的结果!我们所期望的结果是最多只有类A的初始化函数被调用2次——其实这是多继承的类体系必须面对的问题。我们把代码段4的类体系画出来,如下图:

object
  |       \
  |        A
  |      / |
  B  C  D
   \   /   |
     E    |
       \   |
         F

按我们对super的理解,从图中可以看出,在调用类C的初始化函数时,应该是调用类A的初始化函数,但事实上却调用了类D的初始化函数。好一个诡异的问题!

也就是说,mro中记录了一个类的所有基类的类类型序列。查看mro的记录,发觉包含7个元素,7个类名分别为:

F E B C D A object

  从而说明了为什么在C.__init__中使用super(C, self).__init__()会调用类D的初始化函数了。 ???

  我们把代码段4改写为:

代码段5:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class A(object):
    def __init__(self):
        print "enter A"
        super(A, self).__init__()  # new
        print "leave A"
   
class B(object):
    def __init__(self):
        print "enter B"
        super(B, self).__init__()  # new
        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"
        super(E, self).__init__()  # change
        print "leave E"
   
class F(E, D):
    def __init__(self):
        print "enter F"
        super(F, self).__init__()  # change
        print "leave F"

f = F(),执行结果:

enter F enter E enter B enter C enter D enter A leave A leave D leave C leave B leave E leave F

可见,F的初始化不仅完成了所有的父类的调用,而且保证了每一个父类的初始化函数只调用一次。

小结

  1. super并不是一个函数,是一个类名,形如super(B, self)事实上调用了super类的初始化函数,
      产生了一个super对象;
  2. super类的初始化函数并没有做什么特殊的操作,只是简单记录了类类型和具体实例;
  3. super(B, self).func的调用并不是用于调用当前类的父类的func函数;
  4. Python的多继承类是通过mro的方式来保证各个父类的函数被逐一调用,而且保证每个父类函数
      只调用一次(如果每个类都使用super);
  5. 混用super类和非绑定的函数是一个危险行为,这可能导致应该调用的父类函数没有调用或者一
      个父类函数被调用多次。

一些更深入的问题:各位可以看到,print F.__mro__时发现里面元素的顺序是 F E B C D A object,这就是F的基类查找顺序,至于为什么是这样的顺序,以及python内置的多继承顺序是怎么实现的,这涉及到mro顺序的实现,python 2.3以后的版本中是采用的一个叫做C3的算法。

www.qytang.com/
http://www.qytang.com/cn/list/29/
http://www.qytang.com/cn/list/28/428.htm
http://www.qytang.com/cn/list/28/426.htm
http://www.qytang.com/cn/list/28/425.htm
http://www.qytang.com/cn/list/28/424.htm
http://www.qytang.com/cn/list/28/423.htm
http://www.qytang.com/cn/list/28/422.htm
http://www.qytang.com/cn/list/28/421.htm
http://www.qytang.com/cn/list/28/420.htm
http://www.qytang.com/cn/list/28/417.htm
http://www.qytang.com/cn/list/28/416.htm
http://www.qytang.com/cn/list/28/407.htm
http://www.qytang.com/cn/list/28/403.htm

python 多继承详解-乾颐堂的更多相关文章

  1. python时间处理详解-乾颐堂

    1.获取当前时间的两种方法: import datetime,time now = time.strftime("%Y-%m-%d %H:%M:%S") print now now ...

  2. Python 解析配置模块之ConfigParser详解-乾颐堂

    1.基本的读取配置文件 -read(filename) 直接读取ini文件内容 -sections() 得到所有的section,并以列表的形式返回 -options(section) 得到该sect ...

  3. xargs在linux中的使用详解-乾颐堂

    xargs在linux中是个很有用的命令,它经常和其他命令组合起来使用,非常的灵活. xargs是给命令传递参数的一个过滤器,也是组合多个命令的一个工具.它把一个数据流分割为一些足够小的块,以方便过滤 ...

  4. HTTP 499状态码 nginx下499错误详解-乾颐堂

    日志记录中HTTP状态码出现499错误有多种情况,我遇到的一种情况是nginx反代到一个永远打不开的后端,就这样了,日志状态记录是499.发送字节数是0. 老是有用户反映网站系统时好时坏,因为线上的产 ...

  5. Linux ls命令详解-乾颐堂CCIE

      ls命令用法举例: 例一:列出/home文件夹下的所有文件和目录的详细资料: 1 ls -l -R /home 命令参数之前要有一短横线“-”, 上面的命令也可以这样写: 1 ls -lR /ho ...

  6. linux sed命令详解-乾颐堂CCIE

    简介 sed 是一种在线编辑器,它一次处理一行内容.处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的 ...

  7. nginx内置变量详解-乾颐堂

    nginx的配置文件中可以使用的内置变量以美元符$开始,也有人叫全局变量.其中,部分预定义的变量的值是可以改变的. $arg_PARAMETER 这个变量值为:GET请求中变量名PARAMETER参数 ...

  8. Python super继承详解

    MRO(Method resolution order)是python用来解析方法调用顺序的,mro中记录了一个类的所有基类的类类型序列,super不是简单地调用基类的方法,而是按照MRO中的顺序来调 ...

  9. 开发中常遇到的Python陷阱和注意点-乾颐堂

    最近使用Python的过程中遇到了一些坑,例如用datetime.datetime.now()这个可变对象作为函数的默认参数,模块循环依赖等等. 在此记录一下,方便以后查询和补充. 避免可变对象作为默 ...

随机推荐

  1. Kossel的一种滑块位置计算方法

    做了一个小激光雕刻机之后,研究了一下这款3D打印机的结构和工作原理,一下就对这个运动过程很感兴趣,这三个杆是怎么联动使得喷头保持在一个平面上运动呢?打算先做一个架构,然后把激光器放在上面不是可以方便雕 ...

  2. Linut ssh sftp服务重启

    在网上,收了半天,终于找到这个,记录一下~哈~ RedHat Linux 重启SSH /etc/init.d/sshd restart 重启SFTP /etc/init.d/vsftpd restar ...

  3. Java测试用例简介

    最近需要向组内其他成员普及一下关于Java测试用例的相关知识,特在此进行一下简单的学习和总结. JUnit简介 JUnit是一个开源的Java单元测试框架,JUnit4对原有的JUnit框架进行了大幅 ...

  4. CDH5.10 添加kafka服务

    简介: CDH的parcel包中是没有kafka的,kafka被剥离了出来,需要从新下载parcel包安装.或者在线安装,但是在线安装都很慢,这里使用下载parcel包离线安装的方式. PS:kafk ...

  5. Julia - 数学运算

    算术运算符 算术运算符适用于所有的基本数值类型 +x,一元加法,就是 x 本身 -x,一元减法,x 的相反数 x + y,二元加法,做加法运算 x - y,二元减法,做减法运算 x * y,乘法,做乘 ...

  6. Storm开发——环境配置部署

    配置开发环境:http://storm.apache.org/releases/current/Setting-up-development-environment.html 开发环境定义: Stor ...

  7. 爬虫中urllib库

    一.urllib库 urllib是Python自带的一个用于爬虫的库,其主要作用就是可以通过代码模拟浏览器发送请求.其常被用到的子模块在Python3中的为urllib.request和urllib. ...

  8. 查看Java文件对应的字节码

    1. 编译为class文件:javac 2. 使用javap查看bytecode:javap -v

  9. C#中如何判断线程当前所处的状态

    转自原文 在C#中如何判断线程当前所处的状态 在C#中,线程对象Thread使用ThreadState属性指示线程状态,它是带Flags特性的枚举类型对象.    ThreadState 为线程定义了 ...

  10. 使用COM口的2、3针的通断作为中端源(有一个读图像的摄像头,当把卡插到位时触发中端,防止在插卡的过程中出现不稳定的图像)

    利用串口2读,串口3发数据的特点.建立不断的发送流,再从接收端接收.如果收到,则数据畅通,否则断开.相当于产生一个中断.这样电脑对外部事件可作出反应. using System;using Syste ...