Python学习笔记(11)——A的爸爸,B的爸爸
开始之前,问你一个沙雕的问题:
如果你是老陈的爸爸、老王的爸爸、老李的爸爸、老钱的爸爸、老周的爸爸,那么请问你是谁的儿子?
小伙伴:答,我谁也不是。(⊃・ᴥ・)つ
哈哈哈,是不是被整蒙啦,但今天可不是让他们继承你哈~
这次是来带你走进多继承的。
1.用“老赵”认识多继承
如果你有三个孩子,他们分别各自为家,那么关系图就像下面这样:
哈哈哈,请原谅我的联系线。---(∩┌┐∩)---
那么就可以说,赵小聪是赵小静和赵小白的儿子。(为什么呢,-因-为-出-轨-了-呗-~-(假装删除线))
假如说刚才加的那个删除线是真事,那么这就叫多继承。
现在呢,听懂了吧。
多继承就是两个妈妈的孩子。多个父对象的子对象。
2.那么如何多继承呢?
很简单,格式如下:
class OneFather:
pass
class TwoFather:
pass
class Son(OneFather,TwoFather):
pass
比如刚才那个,用代码演示为:
>>> class ZhaoXiaoJing:
... one_type = '赵小静的女儿'
...
>>> class ZhaoXiaoBai:
... two_type = '赵小月的女儿'
...
>>> class ZhaoXiaoCong(ZhaoXiaoJing,ZhaoXiaoYue):
... pass
...
>>> smart = ZhaoXiaoCong()
>>> smart.one_type
'赵小静的女儿'
>>> smart.two_type
'赵小月的女儿'
是不是很简单呢。
hei brother,这么简单我还发文干啥呀,接下来好戏还有更多呢。
3.钻石一样的恶心问题
接下来,我们来看看另一种多继承,称为钻石继承。(这段代码简称为恶心多继承)
class Grandpa:
call_grand = 0
def call(self):
self.call_grand += 1
print('Grandpa called')
class Father1(Grandpa):
call_fa1 = 0
def call(self):
Grandpa.call(self)
self.call_fa1 += 1
print('Father1 called')
class Father2(Grandpa):
call_fa2 = 0
def call(self):
Grandpa.call(self)
self.call_fa2 += 1
print('Father2 called')
class Son(Father1,Father2):
call_son = 0
def call(self):
Father1.call(self)
Father2.call(self)
self.call_son += 1
print('Son called')
if __name__ == '__main__':
son = Son()
son.call()
print([son.call_grand,son.call_fa1,son.call_fa2,son.call_son])
需要注意的是,这段代码里的
+= 1与C中的++效果一样。
结构像这样:
我们想它应该这样运行:
Grandpa called
Father1 called
Father2 called
Son called
[1, 1, 1, 1]
但他妈的跑成这样了:
???
怎么跑成两次了???
玩呢???!!!
骇,这他妈怎么回事,我不玩了。
好啦,这就是本期的内容!
散会~
(咳咳咳,这个表情包用几次了??)
好啦,竟然你翻到这里来了,就继续吧。
要知道,kzx是那种不讲情义的人吗~
3.1.MRO是什么鬼
要想开始,我们需要了解一个知识点:Method resolution order,简称MRO 。
???
MRO???什么鬼???
等一下,我来翻译一下:方法解析顺序。
MRO会在此方法左右干找找不到时,向前搜它的父亲、爷爷、曾祖、高祖……直至鼻祖(Python3默认会给所有的类增加一个父类,名为object,尤其表现在最前端的父类上,这就是新式类。而旧式类不会添加object)。
一般情况下,这个顺序有好多种情况:
- 从子类向前搜索。这种情况很简单,是单继承搜索方法,如:
C->B->A。 - 搜索子类的第一个父类,如果没有,则搜索父类的父类,直到搜索到最前端的父类(不包括object),开始照样搜索第二个父类,直到所有父类全部搜索完,如:
C->B->Base->A->Base。这种方式为Depth first search(DFS,深度优先搜索) 。 - 使用DFS全部搜索,然后保留重复类中最后的类,如:
C->B->A->Base。这种方法属于新式类。 - 使用
merge()为搜索顺序排序。这种方式为C3,是至今唯一一个留存住的方式。
接下来,我们主要讲解C3的顺序。
3.2.C3???merge()???
很多人看到C3与merge(),都是一脸懵逼。
哈哈哈,别那么懵逼呀,我还没讲呢。(⊃・ᴥ・)つ
我们的C3公式,可记为:L[CLASS] = [CLASS] + merge(L(1),L[2],L[3],……,[1],[2],[3],……)。
而计算出MRO顺序,可以:
- L[类] = [类] + merge(L[1],L[2],……,[1],[2],……)
- L[类] = [类] + merge([1,1和2共同的父类],[2,1和2共同的父类],[1和2共同的父类,object],[1,2])
- L[类] = [类,1] + merge([1和2共同的父类],[2,1和2共同的父类],[1和2共同的父类,object],[2])
- L[类] = [类,1,2] + merge([1和2共同的父类],[1和2共同的父类],[1和2共同的父类,object])
- L[类] = [类,1,2,1和2共同的父类] + merge([object])
- L[类] = [类,1,2,1和2共同的父类,object]
需要注意的是,3-6步从前往后,先查找merge的第一个列表的第一个值有没有重复类,如果有,将它全舍去,将第一个[类]添加上这个值,以此类推,直到推出object即可。
假如C父类为A和B,就可以这么计算:
L[C] = [C] + merge(L[A],L[B],[A],[B])
= [C] + merge([A,object],[B,A],[A,B])
= [C,A] + merge([object],[B],[B])
= [C,A,B] + merge([object])
= [C,A,B,object]
最后得出顺序为[C,A,B,object]。
我们可以验证一下,比如用Python的魔法函数__mro__,可以得出顺序:
怎么样。
而我们刚才那个恶心多继承,我们可以计算一下Son类的mro顺序:
L[Son] = [Son] + merge(L[Father1],L[Father2],[Father1],[Father2])
= [Son] + merge([Father1,Grandpa],[Father2,Grandpa],[Grandpa,object],[Father1,Father2])
= [Son,Father1] + merge([Grandpa],[Father2,Grandpa],[object],[Father2])
= [Son,Father1,Father2] + merge([Grandpa],[object],[Grandpa])
= [Son,Father1,Father2,Grandpa] + merge([object])
= [Son,Father1,Father2,Grandpa,object]
验证:
现在知道了吧,它先执行Father1,再执行Father2,而它们俩都调用了Grandpa,就会调用两次。
我们也得出了真理:男人永远不会生孩子。object永远是最后。
那么如何让它只调用一次呢?
super()可能是个好办法。
3.3.super()永远的神
super其实是个类,但它会继承你的父类方法。格式如下:
super().一个函数()
我们来试一试,把恶心多继承的代码改成:
class Grandpa:
call_grand = 0
def call(self):
self.call_grand += 1
print('Grandpa called')
class Father1(Grandpa):
call_fa1 = 0
def call(self):
#Grandpa.call(self)
super().call()
self.call_fa1 += 1
print('Father1 called')
class Father2(Grandpa):
call_fa2 = 0
def call(self):
#Grandpa.call(self)
super().call()
self.call_fa2 += 1
print('Father2 called')
class Son(Father1,Father2):
call_son = 0
def call(self):
'''
Father1.call(self)
Father2.call(self)
'''
super().call()
self.call_son += 1
print('Son called')
if __name__ == '__main__':
son = Son()
son.call()
print([son.call_grand,son.call_fa1,son.call_fa2,son.call_son])
跑起来:
是不是很神奇,它把两次的super().call()合并起来了。
诶呦!这期严重超长了。
这就是本期的内容!
散会~









