sunrise085 发表于 2020-4-29 10:33:50

Python细节之6、类的多继承中super函数的调用顺序

本帖最后由 sunrise085 于 2020-4-29 14:09 编辑

Python细节之6、类的多继承中super函数的调用顺序
好久没有发过帖子了。
不说废话,直奔主题。

一、类的继承中,子类对象初始化

面向对象编程的过程中,不免会用到类的继承,尤其是多重继承,关于子类的初始化会有这么几种情况:
情况一:子类需要自动调用父类的方法:子类不重写__init__()方法,实例化子类后,会自动调用父类的 __init__() 的方法。
情况二:子类不需要自动调用父类的方法:子类重写__init__()方法,实例化子类后,将不会自动调用父类的 __init__() 的方法。
情况三:子类重写 __init__() 方法又需要调用父类的方法,这时候就需要显式地调用父类的 __init__() 方法。

调用父类的 __init__() 方法,有两种方式:一种是直接使用父类名. __init__();一种是使用 super(). __init__()。
父类名. __init__()这种方式,更加直接,更加定向地去调用对应的 __init__() 函数。但是可能会遇到重复调用的问题,甚至可能会进入死循环。

二、使用 super() 调用父类的初始化函数 __init__()

super(). __init__() 这种方式,则会按照一定的规则去调用相应的 __init__() 函数。
super(). __init__() 所遵循的规则是什么呢?我们需要先看一下 __mro__ 列表(有鱼油提出, __mro__ 是一个元组不是列表,的确在 Python 中它的类型是元组,不过这里是为了更加方便去理解 super 如何去查表执行的,你若感觉不应称之为列表,那就称之为元组吧)。__mro__列表是个什么鬼呢? 它是多重继承中,子类中继承父类的顺序,可以通过 子类. __mro__ 查看该列表。当子类调用 __init__() 函数过程中,若是遇到 super(). __init__()则会按照 __mro__ 列表的顺序去调用相对应的父类的 __init__() 函数。在执行过程中,每遇到一次 super,就会按照 __mro__ 列表的顺序去调用下一个类的 __init__()。
举个例子,给出这样一个继承关系图,如下图所示:


按照上图的继承关系,看看下面的程序,分析一下 F 类的 __mro__ 列表的顺序是什么,程序的执行过程又是怎样的呢?
class A():
    def __init__(self):
      print("进入A…")
      print("离开A…")

class G():
    def __init__(self):
      print("进入G…")
      super().__init__()
      print("离开G…")
      
class B(A):
    def __init__(self):
      print("进入B…")
      super().__init__()
      print("离开B…")
      
class C(A):
    def __init__(self):
      print("进入C…")
      super().__init__()
      print("离开C…")

class D(B, C):
    def __init__(self):
      print("进入D…")
      print("离开D…")

class E(G):
    def __init__(self):
      print("进入E…")
      super().__init__()
      print("离开E…")

class F(E,D):
    def __init__(self):
      print("进入F…")
      super().__init__()
      print("离开F…")      

print(F.__mro__)
d = F()

你可以先按照上面所说的,自己想一下运行的结果会是什么,然后再看下面的答案。
**** Hidden Message *****
三、使用含参数的 super() 调用父类的初始化函数 __init__()

实际上,使用 super 的时候也可以不完全按照 __mro__() 列表执行。因为 super 有两个参数,第一个参数是父类名,第二个参数是实例化参数self,可以根据第一个参数跳跃去执行对应类的 __init__()。
具体会怎么跳呢?我们还以刚刚的这个继承关系为例。修改一下各个类的初始化函数 __init__()。
class A():
    def __init__(self):
      print("进入A…")
      print("离开A…")

class G():
    def __init__(self):
      print("进入G…")
      print("离开G…")
      
class B(A):
    def __init__(self):
      print("进入B…")
      super(C,self).__init__()
      print("离开B…")
      
class C(A):
    def __init__(self):
      print("进入C…")
      super(D,self).__init__()
      print("离开C…")

class D(B, C):
    def __init__(self):
      print("进入D…")
      super().__init__()
      #super(A,self).__init__()
      print("离开D…")

class E(G):
    def __init__(self):
      print("进入E…")
      #super().__init__()
      super(B,self).__init__()
      print("离开E…")

class F(E,D):
    def __init__(self):
      print("进入F…")
      super().__init__()
      #super(G,self).__init__()
      print("离开F…")      
print(F.__mro__)
d = F()
可以看到我们修改之后,有些类的 __init__() 函数中在调用 super 的时候加上了参数。例如 class E 中是 super(B,self).__init__()。这个是去执行 class B 的初始化函数吗? NO!!自己先思考一下,再看下面的结果,看看是否和你想象的一样
**** Hidden Message *****
关于这种复杂继承的初始化问题,这里只是进行了方法上的研究,在我们平时学习的过程中应该很少遇到。在今后工作过程中一旦遇到这么复杂的继承关系,那么将会更加复杂,因为初始化还会涉及到数据成员的初始化赋值等很多问题。
以上就是我所理解的super相关的用法。若有不足之处,欢迎各位批评指正。

Twilight6 发表于 2020-4-29 10:35:05

沙发

sunrise085 发表于 2020-4-29 10:38:44

Twilight6 发表于 2020-4-29 10:35
沙发

你可真快,我帖子还没编辑完呢
发帖之后,我还在看有没有错误呢,你也快帮我看看,有没有错别字之类的

永恒的蓝色梦想 发表于 2020-4-29 10:49:18

前排支持~

永恒的蓝色梦想 发表于 2020-4-29 10:50:25

__mro__ 不是个元组嘛{:10_324:}

sunrise085 发表于 2020-4-29 10:55:49

永恒的蓝色梦想 发表于 2020-4-29 10:50
__mro__ 不是个元组嘛

对滴。__mro__在python变量分类中是元组。
这里之所以称之为列表,是因为我们平常所说的都是顺序查表,没有说顺序查组的。
实际上元组就是一个有序列表(这里所谓的列表不是python的一个类型哟)
若严格来说的话,或许的确应该把帖子中的__mro__列表改为__mro__元组。

永恒的蓝色梦想 发表于 2020-4-29 10:58:33

为了免去乱七八糟的继承烦恼,我建议大家调用时直接用父类名调用{:10_256:}
开个玩笑

sunrise085 发表于 2020-4-29 11:00:31

永恒的蓝色梦想 发表于 2020-4-29 10:58
为了免去乱七八糟的继承烦恼,我建议大家调用时直接用父类名调用
开个玩笑

看帖子中黄底 的那一行字~~

永恒的蓝色梦想 发表于 2020-4-29 11:02:58

sunrise085 发表于 2020-4-29 11:00
看帖子中黄底 的那一行字~~

emmm……什么时候会出现这种情况呢?

sunrise085 发表于 2020-4-29 11:13:05

永恒的蓝色梦想 发表于 2020-4-29 11:02
emmm……什么时候会出现这种情况呢?

听说过钻石继承吗?
就像这个图中说话的ABCD四个类的继承方式那样,是个菱形的,称之为钻石继承
若用父类名调用__init__,那么A的初始化__init__会被调用两次:D调用B调用A,调用C调用A

永恒的蓝色梦想 发表于 2020-4-29 11:24:06

sunrise085 发表于 2020-4-29 11:13
听说过钻石继承吗?
就像这个图中说话的ABCD四个类的继承方式那样,是个菱形的,称之为钻石继承
若用父 ...

emmmm……然而 super() 也无法解决这个问题。
你说的这种情况只会在B.__init__(self)
C.__init__(self)的时候出现

但是 super 成功一次立马停手,所以说 super 并不能解决这个问题。

sunrise085 发表于 2020-4-29 11:25:57

永恒的蓝色梦想 发表于 2020-4-29 11:24
emmmm……然而 super() 也无法解决这个问题。
你说的这种情况只会在的时候出现



super没有解决重复调用的问题?

永恒的蓝色梦想 发表于 2020-4-29 11:29:25

sunrise085 发表于 2020-4-29 11:25
super没有解决重复调用的问题?

我好像理解错了什么……抱歉搞错了

sunrise085 发表于 2020-4-29 11:31:31

永恒的蓝色梦想 发表于 2020-4-29 11:29
你手动重复调用 super 也没办法啊

。。。。。
你这就是抬杠了。。。。。
我也不再说什么了。。。。
。。。。。

永恒的蓝色梦想 发表于 2020-4-29 11:32:18

sunrise085 发表于 2020-4-29 11:31
。。。。。
你这就是抬杠了。。。。。
我也不再说什么了。。。。


{:10_282:}抱歉搞错了{:10_245:}

sunrise085 发表于 2020-4-29 14:14:04

@zltzlt 申精。第一次申精,按照申精帖调整了一下格式,但是不知道是否符合条件~~

zltzlt 发表于 2020-4-29 17:07:05

@小甲鱼 @不二如是

zltzlt 发表于 2020-4-29 17:13:04

sunrise085 发表于 2020-4-29 14:14
@zltzlt 申精。第一次申精,按照申精帖调整了一下格式,但是不知道是否符合条件~~

我没有权限加精华,最终还要管理员大大审核

sunrise085 发表于 2020-4-29 17:16:15

zltzlt 发表于 2020-4-29 17:13
我没有权限加精华,最终还要管理员大大审核

哦哦,多谢帮忙@管理员。我还没加管理员好友呢,无法@

fred2020 发表于 2020-4-30 11:41:53

厉害了
页: [1] 2 3 4 5
查看完整版本: Python细节之6、类的多继承中super函数的调用顺序