(转)Python: super 没那么简单

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

python

说到 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(Method Resolution Order) 列表以及 MRO 中的一个类。当以如下方式调用 super 时:

super(a_type, obj)

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

当这样调用时:

super(type1, type2)

MRO 指的是 type2 的 MRO 列表, MRO 中的那个类就是 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)

现在你知道为什么 d.add(2) 后 d.n 的值是 19 了吧 ;)

That‘s all! 希望这篇文章能对你有所帮助 ;)

参考资料

原文地址:https://www.cnblogs.com/liujiacai/p/10043935.html

时间: 2024-10-10 19:47:56

(转)Python: super 没那么简单的相关文章

python学习之最简单的获取本机ip信息的小程序

文章是从我的个人博客粘贴过来的,大家可以直接访问我的个人博客哦 http://www.iwangzheng.com 获取本机ip信息的命令ifconfig总是在用,这次拿到pyhton代码里,感觉python好麻烦啊,每次都要import. 可以在程序里直接read出来,也可以先在命令行执行ifconfig > xx.txt,这样可以直接访问倒入后的文本信息. 这个小程序有个问题,与朋友讨论了还没解决彻底,如果用findall是可以匹配所有的,但是怎么解决用match只匹配一次的问题呢,当然如果

Python super继承详解

MRO(Method resolution order)是python用来解析方法调用顺序的,mro中记录了一个类的所有基类的类类型序列,super不是简单地调用基类的方法,而是按照MRO中的顺序来调用类的方法.使用super()时,应该在所有类中使用,否则就可能发生有的类构造函数没有调用的情况.#!/usr/bin/python# -*- coding: utf-8 -*-class A(object): def __init__(self): print 'A __init__' super

Python ---- super()使用

我们经常在类的继承当中使用super(), 来调用父类中的方法.例如下面: 1 2 3 4 5 6 7 8 9 10 11 12 13 class A:     def func(self):         print('OldBoy') class B(A):     def func(self):         super().func()         print('LuffyCity') A().func() B().func() 输出的结果为: OldBoy OldBoy Luf

Python super() 函数

Python super() 函数  Python 内置函数 描述 super() 函数是用于调用父类(超类)的一个方法. super 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO).重复调用(钻石继承)等种种问题. MRO 就是类的方法解析顺序表, 其实也就是继承父类方法时的顺序表. 语法 以下是 super() 方法的语法: super(type[, object-or-type]) 参数 type -- 类. obj

python学习之最简单的用户注册及登录验证小程序

文章都是从我的个人博客上粘贴过来的哦,更多内容请点击 http://www.iwangzheng.com 正如很多同学所知道的,楼主开始学习python了,前进的道路曲曲折折,有荆棘也有陷阱,从最简单的小程序写起,每天练习,将python进行到底. 有一点比较别扭的就是python的换行之后空四个空格,ruby都是两个,并且python在方法和循环语句的第一句都要加冒号 mysql> show create table user; mysql> alter table user add sal

研磨设计模式解析及python代码实现——(一)简单工厂模式

最近在学设计模式,正巧书之前学了些python,但用的还不是很成熟.<研磨设计模式>书上只给了java代码,本着以练手为目标,我照着书上打了一遍java代码,在仔细体会其思想后,将其写成了python的代码.有不对的地方希望各位批评指正~ 具体原理不多做介绍,具体可以参考http://chjavach.iteye.com的博客,或者<研磨设计模式>的书. 一.背景介绍 接口思想: 众所周知,面向对象语言最大的特点便是封装,继承,多态这三个概念.而像Java等面向对象语言最核心的思想

缘来幸福没那么简单

曾以为幸福一直在手中,可以信手拈来,但是原来幸福没那么简单,尤其是要等属于自己的缘来,幸福没那么简单.幸福要等待,要争取,要经营,并非理所当然,并非会一直停在那里等我去拿.  当我刚离开父母追求属于我的生活,属于我的幸福时,刚开始我觉得这是一件轻而易举的事情.我相信我可以轻松地重新找到一份我喜欢的工作,遇到一个适合我的人,然后事业爱情双丰收,就这样幸福地生活下去.我绝不会如同父母所说的那样后悔放弃已经拥有的一切,选择到一个陌生的城市重新开始的,一定要向父母证明我一定会得到幸福.  然而,当我求职

Python爬虫之路——简单的网页抓图

转载自我自己的博客:http://www.mylonly.com/archives/1401.html 用Python的urllib2库和HTMLParser库写了一个简单的抓图脚本,主要抓的是http://desk.zol.com.cn/meinv/这个链接下的图片,通过得到图集的起始URL地址,得到第一张图片,然后不断的去获取其下一个图片的URL,继而得到所有首页的图集的图片. 整个源码如下,比较简单,写这个只是简单的练手而已 #coding: utf-8 #################

Python爬虫之路——简单网页抓图升级版(增加多线程支持)

转载自我的博客:http://www.mylonly.com/archives/1418.html 经过两个晚上的奋斗,将上一篇文章介绍的爬虫稍微改进了下(Python爬虫之路--简单网页抓图),主要是将获取图片链接任务和下载图片任务用线程分开来处理了,而且这次的爬虫不仅仅可以爬第一页的图片链接的,整个http://desk.zol.com.cn/meinv/下面的图片都会被爬到,而且提供了多种分辨率图片的文件下载,具体设置方法代码注释里面有介绍. 这次的代码仍然有点不足,Ctrl-C无法终止程