Python之super与MRO

1.为什么要有super

MRO 的作用:决定基类中的函数到底应该以什么样的顺序调用父类中的函数

super()可以用于调用父类(超类)的某个方法,并且只能用于新式类(后面内容会讲经典类与新式类),主要用在多继承中,在单继承时直接调用父类方法即可,但是在多继承中就会涉及到重复继承等问题,这里也需要牵涉到MRO(Method Resolution Order,方法解析顺序)。super()形式如下:super(type[, object-or-type]),type -- 类,object-or-type -- 类,一般是 self,如下

 

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()

在这个例子中,Net类继承nn.Module,并且调用nn.Module的初始化方法,但是这是一个简单的单继承关系,但是更复杂的情况下,比如多继承时,该如何调用方法呢?(调用方法,需要对当前类和基类进行搜索以确定方法所在的位置)下图为钻石继承,类D继承类B和类C,类B和类C都继承A,类C和类A都有方法method,那么D.method()到底调用的是A.method()还是C .method()?如果按照D->B->A->C的顺序将会调用A.method(),按照D->B->C->A的顺序将会调用C.method()。下面来研究一下Python的MRO,到底是如何进行的。

 

Python有如下的MRO:

  1. Python 2.2以前的经典类
  2. Python 2.2中的新式类
  3. Python 2.3及其以后新式类的C3 算法,Python 3 唯一支持的形式

经典类中的MRO

经典类的新式类的不同之处在于,新式类继承自object,在python 2.1及以前只有经典类,经典类的MRO采取自左向右的深度遍历,在上述钻石继承中,MRO顺序为D->B->A->C->A,去除重复结果:D->B->A->C,在经典类中D.method()调用的是A.method()。但是如果有这样一个问题,C.method()在A.method()基础上增加了新的功能(比如重写了这些属性或者方法),那么使用D.method()无法调用到C.method()。

import inspect
class A:
    def method(self):
        print 'This is a method of A'

class B(A):
    pass

class C(A):
    def method(self):
        print 'This is a method of C'

class D(B, C):
    pass

x = D()
x.method()
print inspect.getmro(D) 

Python 2.2中的新式类MRO

对于新式类,所有类都要继承obejct,需要在创建类时继承object,同时可以使用__mro__查看MRO,对于新式类的MRO,将是自左向右的广度遍历,上述钻石继承的顺序就变成了D->B->C->A->object,解决了钻石继承在经典类中存在的问题。但是对于正常的继承关系:

 

根据新式类中的广度遍历原则,E->C->D->A->B->object,A是C的唯一基类,但却在C之后先查询D,根据单调性,应该先从唯一基类进行查找。注意:在Python 2.2及其以后的版本仍然存在经典类,在定义基类时,如果继承object才是新式类,否则是经典类

Python 2.3及其以后的新式类MRO

在Python 2.3及其以后的版本中,新式类的MRO使用C3算法,并且在Python 3中只存在新式类。Python 2.3及其以后的新式类MRO,使用的是拓扑排序,在一个有向无环图中:

  1. 选择一个入度为0的顶点并输出(入度:以某顶点为弧头,终止于该顶点的弧的数目)
  2. 从网中删除此顶点以及所有出边
  3. 重复步骤1、2,直到所有点都被遍历

 

新算法与基于深度遍历的算法类似,但是不同在于新算法会对深度优先遍历得到的搜索路径进行额外的检查。其从左到右扫描得到的搜索路径,对于每一个节点解释器都会判断该节点是不是好的节点。如果不是好的节点,那么将其从当前的搜索路径中移除。

那么问题在于,什么是一个好的节点?我们说 N 是一个好的节点当且仅当搜索路径中 N 之后的节点都不继承自 N。我们还以上述的类继承图为例,按照深度优先遍历得到类 D 中函数的搜索路径 D, B, A, C, A。之后 Python 解释器从左向右检查时发现第三个节点 A 不是一个好的节点,因为 A 之后的节点 C 继承自 A。因此其将 A 从搜索路径中移除,然后得到最后的调用顺序 D, B, C, A。

原文地址:https://www.zhihu.com/question/20040039

针对你的问题,答案是可以,并没有区别。但是这题下的回答我感觉都不够好。

要谈论 super,首先我们应该无视 "super" 这个名字带给我们的干扰。

不要一说到 super 就想到父类!super 指的是 MRO 中的下一个类!
不要一说到 super 就想到父类!super 指的是 MRO 中的下一个类!
不要一说到 super 就想到父类!super 指的是 MRO 中的下一个类!

一说到 super 就想到父类这是初学者很容易犯的一个错误,也是我当年犯的错误。
忘记了这件事之后,再去看这篇文章:Python’s super() considered super!
这是 Raymond Hettinger 写的一篇文章,也是全世界公认的对 super 讲解最透彻的一篇文章,凡是讨论 super 都一定会提到它(当然还有一篇 Python's Super Considered Harmful)。

如果不想看长篇大论就去看这个答案,super 其实干的是这件事:



def super(cls, inst):
    mro = inst.__class__.mro()
    return mro[mro.index(cls) + 1]

两个参数 cls 和 inst 分别做了两件事:
1. inst 负责生成 MRO 的 list
2. 通过 cls 定位当前 MRO 中的 index, 并返回 mro[index + 1]
这两件事才是 super 的实质,一定要记住!
MRO 全称 Method Resolution Order,它代表了类继承的顺序。后面详细说。



class Root(object):
    def __init__(self):
        print("this is Root")

class B(Root):
    def __init__(self):
        print("enter B")
        # print(self)  # this will print <__main__.D object at 0x...>
        super(B, self).__init__()
        print("leave B")
        
class C(Root):
    def __init__(self):
        print("enter C")
        super(C, self).__init__()
        print("leave C")
        
class D(B, C):
    pass
        
d = D()
print(d.__class__.__mro__)

输出

enter B
enter C
this is Root
leave C
leave B
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.Root'>, <type 'object'>)

知道了 super 和父类其实没有实质关联之后,我们就不难理解为什么 enter B 下一句是 enter C 而不是 this is Root(如果认为 super 代表“调用父类的方法”,会想当然的认为下一句应该是this is Root)。流程如下,在 B 的 __init__ 函数中:

super(B, self).__init__()

首先,我们获取 self.__class__.__mro__,注意这里的 self 是 D 的 instance 而不是 B 的

(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.Root'>, <type 'object'>)

然后,通过 B 来定位 MRO 中的 index,并找到下一个。显然 B 的下一个是 C。于是,我们调用 C 的 __init__,打出 enter C。

顺便说一句为什么 B 的 __init__ 会被调用:因为 D 没有定义 __init__,所以会在 MRO 中找下一个类,去查看它有没有定义 __init__,也就是去调用 B 的 __init__。

其实这一切逻辑还是很清晰的,关键是理解 super 到底做了什么。

于是,MRO 中类的顺序到底是怎么排的呢?Python’s super() considered super!中已经有很好的解释,我翻译一下:
在 MRO 中,基类永远出现在派生类后面,如果有多个基类,基类的相对顺序保持不变。

 

最后的最后,提醒大家.
什么 super 啊,MRO 啊,都是针对 new-style class。如果不是 new-style class,就老老实实用父类的类名去调用函数吧

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值