转至:https://mozillazg.com/2016/12/python-super-is-not-as-simple-as-you-thought.html

说到 super, 大家可能觉得很简单呀,不就是用来调用父类方法的嘛。如果真的这么简单的话也就不会有这篇文章了,且听我细细道来。

约定

在开始之前我们来约定一下本文所使用的 Python 版本。默认用的是 Python 3,也就是说:本文所定义的类都是新式类。如果你用到是 Python 2 的话,记得继承 object:

 # 默认, Python 3
class A:
pass # Python 2
class A(object):
pass

Python 3 和 Python 2 的另一个区别是: Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx :

 # 默认,Python 3
class B(A):
def add(self, x):
super().add(x) # Python 2
class B(A):
def add(self, x):
super(B, self).add(x)

所以,你如果用的是 Python 2 的话,记得将本文的 super() 替换为 suepr(Class, self) 。

如果还有其他不兼容 Python 2 的情况,我会在文中注明的。

单继承

在单继承中 super 就像大家所想的那样,主要是用来调用父类的方法的。

class A:
def __init__(self):
self.n = 2 def add(self, m):
print('self is {0} @A.add'.format(self))
self.n += m class B(A):
def __init__(self):
self.n = 3 def add(self, m):
print('self is {0} @B.add'.format(self))
super().add(m)
self.n += 3

你觉得执行下面代码后, b.n 的值是多少呢?

b = B()
b.add(2)
print(b.n)

执行结果如下:

self is <__main__.B object at 0x106c49b38> @B.add
self is <__main__.B object at 0x106c49b38> @A.add
8

这个结果说明了两个问题:

  • 1、super().add(m) 确实调用了父类 A 的 add 方法。
  • 2、super().add(m) 调用父类方法 def add(self, m) 时, 此时父类中 self 并不是父类的实例而是子类的实例, 所以 b.add(2) 之后的结果是 5 而不是 4 。

不知道这个结果是否和你想到一样呢?下面我们来看一个多继承的例子。

多继承

这次我们再定义一个 class C,一个 class D:

class C(A):
def __init__(self):
self.n = 4 def add(self, m):
print('self is {0} @C.add'.format(self))
super().add(m)
self.n += 4 class D(B, C):
def __init__(self):
self.n = 5 def add(self, m):
print('self is {0} @D.add'.format(self))
super().add(m)
self.n += 5

下面的代码又输出啥呢?

d = D()
d.add(2)
print(d.n)

这次的输出如下:

self is <__main__.D object at 0x10ce10e48> @D.add
self is <__main__.D object at 0x10ce10e48> @B.add
self is <__main__.D object at 0x10ce10e48> @C.add
self is <__main__.D object at 0x10ce10e48> @A.add
19

你说对了吗?你可能会认为上面代码的输出类似:

self is <__main__.D object at 0x10ce10e48> @D.add
self is <__main__.D object at 0x10ce10e48> @B.add
self is <__main__.D object at 0x10ce10e48> @A.add
15

为什么会跟预期的不一样呢?下面我们将一起来看看 super 的奥秘。

super 是个类

当我们调用 super() 的时候,实际上是实例化了一个 super 类。你没看错, super 是个类,既不是关键字也不是函数等其他数据结构:

>>> class A: pass
...
>>> s = super(A)
>>> type(s)
<class 'super'>
>>>

在大多数情况下, super 包含了两个非常重要的信息: 一个 MRO 以及 MRO 中的一个类。当以如下方式调用 super时:

super(a_type, obj)

MRO 指的是 type(obj) 的 MRO, MRO 中的那个类就是 a_type , 同时 isinstance(obj, a_type) == True。

当这样调用时:

super(type1, type2)

MRO 指的是 type2 的 MROMRO 中的那个类就是 type1 ,同时 issubclass(type2, type1) == True 。

那么, super() 实际上做了啥呢?简单来说就是:提供一个 MRO 以及一个 MRO 中的类 C , super() 将返回一个从 MRO 中 C 之后的类中查找方法的对象。

也就是说,查找方式时不是像常规方法一样从所有的 MRO 类中查找,而是从 MRO 的 tail 中查找。

举个例子, 有个 MRO:

[A, B, C, D, E, object]

下面的调用:

super(C, A).foo()

super 只会从 C 之后查找,即: 只会在 D 或 E 或 object 中查找 foo 方法。

多继承中 super 的工作方式

再回到前面的

d = D()
d.add(2)
print(d.n)

现在你可能已经有点眉目,为什么输出会是这样了吧

self is <__main__.D object at 0x10ce10e48> @D.add
self is <__main__.D object at 0x10ce10e48> @B.add
self is <__main__.D object at 0x10ce10e48> @C.add
self is <__main__.D object at 0x10ce10e48> @A.add
19

下面我们来具体分析一下:

  • D 的 MRO 是: [D, B, C, A, object] 。 备注: 可以通过 D.mro() (Python 2 使用 D.__mro__ ) 来查看 D 的 MRO 信息)

  • 详细的代码分析如下:

    class A:
    def __init__(self):
    self.n = 2 def add(self, m):
    # 第四步
    # 来自 D.add 中的 super
    # self == d, self.n == d.n == 5
    print('self is {0} @A.add'.format(self))
    self.n += m
    # d.n == 7 class B(A):
    def __init__(self):
    self.n = 3 def add(self, m):
    # 第二步
    # 来自 D.add 中的 super
    # self == d, self.n == d.n == 5
    print('self is {0} @B.add'.format(self))
    # 等价于 suepr(B, self).add(m)
    # self 的 MRO 是 [D, B, C, A, object]
    # 从 B 之后的 [C, A, object] 中查找 add 方法
    super().add(m) # 第六步
    # d.n = 11
    self.n += 3
    # d.n = 14 class C(A):
    def __init__(self):
    self.n = 4 def add(self, m):
    # 第三步
    # 来自 B.add 中的 super
    # self == d, self.n == d.n == 5
    print('self is {0} @C.add'.format(self))
    # 等价于 suepr(C, self).add(m)
    # self 的 MRO 是 [D, B, C, A, object]
    # 从 C 之后的 [A, object] 中查找 add 方法
    super().add(m) # 第五步
    # d.n = 7
    self.n += 4
    # d.n = 11 class D(B, C):
    def __init__(self):
    self.n = 5 def add(self, m):
    # 第一步
    print('self is {0} @D.add'.format(self))
    # 等价于 super(D, self).add(m)
    # self 的 MRO 是 [D, B, C, A, object]
    # 从 D 之后的 [B, C, A, object] 中查找 add 方法
    super().add(m) # 第七步
    # d.n = 14
    self.n += 5
    # self.n = 19 d = D()
    d.add(2)
    print(d.n)

    调用过程图如下:

    D.mro() == [D, B, C, A, object]
    d = D()
    d.n == 5
    d.add(2) class D(B, C): class B(A): class C(A): class A:
    def add(self, m): def add(self, m): def add(self, m): def add(self, m):
    super().add(m) 1.---> super().add(m) 2.---> super().add(m) 3.---> self.n += m
    self.n += 5 <------6. self.n += 3 <----5. self.n += 4 <----4. <--|
    (14+5=19) (11+3=14) (7+4=11) (5+2=7)

Python中super详解的更多相关文章

  1. Python中dict详解

    from:http://www.cnblogs.com/yangyongzhi/archive/2012/09/17/2688326.html Python中dict详解 python3.0以上,pr ...

  2. 2.python中self详解(程序适用于python3版本)

    先介绍下Python中的类和实例面向对象最重要的概念就是类(class)和实例(instance),类(class)是抽象的模板,比如学生这个抽象的事物,可以用一个Student类来表示.而实例是根据 ...

  3. python中闭包详解

    谈谈自己的理解:python中闭包,闭包的实质   闭包这个概念好难理解,身边朋友们好多都稀里糊涂的,稀里糊涂的林老冷希望写下这篇文章能够对稀里糊涂的伙伴们有一些帮助~ 请大家跟我理解一下,如果在一个 ...

  4. python中进程详解

    1:pdb调试:基于命令行的调试工具,非常类似gnu和gdb调试,以下是常用的调试命令: 可以python -m pdb xxx.py(你的py文件名)进入命令行调试模式 命令 简写命令 作用 bea ...

  5. python中assert详解

    assert基础 官方解释:"Assert statements are a convenient way to insert debugging assertions into a pro ...

  6. (转)python中@property详解

    转:https://www.cnblogs.com/zhangfengxian/p/10199935.html

  7. Objective-C中 Self和 Super详解

    Objective-C中 Self和 Super详解 Objective-C 中Self 和 Super 详解本文要介绍的内容,在 Objective-C 中的类实现中经常看到这两个关键字 self  ...

  8. 【python进阶】详解元类及其应用2

    前言 在上一篇文章[python进阶]详解元类及其应用1中,我们提到了关于元类的一些前置知识,介绍了类对象,动态创建类,使用type创建类,这一节我们将继续接着上文来讲~~~ 5.使⽤type创建带有 ...

  9. 第十一章、super()详解

    目录 第十一章.super()详解 一.引出super()来由 第十一章.super()详解 一.引出super()来由 原始用法: 在python类的方法中,要调用父类的某个方法,通常是类.方法() ...

随机推荐

  1. 什么是虚拟环境、为什么使用虚拟环境、Anaconda创建、激活、退出、删除虚拟环境

    一.虚拟环境 virtual environment 它是一个虚拟化,从电脑独立开辟出来的环境.通俗的来讲,虚拟环境就是借助虚拟机docker来把一部分内容独立出来,我们把这部分独立出来的东西称作“容 ...

  2. [深度学习]Python/Theano实现逻辑回归网络的代码分析

    2014-07-21 10:28:34 首先PO上主要Python代码(2.7), 这个代码在Deep Learning上可以找到. # allocate symbolic variables for ...

  3. hadoop之 HDFS fs 命令总结

    版本:Hadoop 2.7.4 -- 查看hadoop fs帮助信息[root@hadp-master sbin]# hadoop fsUsage: hadoop fs [generic option ...

  4. Spark Streaming性能调优

    数据接收并行度调优(一) 通过网络接收数据时(比如Kafka.Flume),会将数据反序列化,并存储在Spark的内存中.如果数据接收称为系统的瓶颈,那么可以考虑并行化数据接收.每一个输入DStrea ...

  5. Unit05: WEB项目的开发模式 、转发 和 Unit09: EL、JSTL

    Unit05: WEB项目的开发模式 .转发   和  Unit09: EL.JSTL dao package dao; import java.io.Serializable; import jav ...

  6. Unit04: JavaScript 概述 、 JavaScript 基础语法 、 流程控制

    Unit04: JavaScript 概述 . JavaScript 基础语法 . 流程控制 my.js function f3() { alert("唐胜伟"); } demo1 ...

  7. Android UI一些技巧

    (1)去掉EditText的背景颜色  android:background="@null" (2)ListView 修改某行的值,因为一些功能逻辑,需要修改ListView某行的 ...

  8. java代码---------打印正三角形

    总结:手打一遍 public class aaa{ public static void main(String []args){ for(int i=1;i<10;i++){ for(int ...

  9. node的socket.io的广播消息

    在多个客户端与服务器端建立连接后,socket.io()服务器具有一个sockets属性,属性值为所有与客户端建立连接的socket对象.可以利用该对象的send方法或emit方法向所有客户端广播消息 ...

  10. 短URL链接系统

    定义: 短网址(Short URL),顾名思义就是在形式上比较短的网址.但不知道有多少人像我一样,由于面试问道才知道有这种系统而对短连接原理好奇,从而进行进一步的研究.在Web 2.0的今天,不得不说 ...