Python学习笔记(11)——A的爸爸,B的爸爸

开始之前,问你一个沙雕的问题:

如果你是老陈的爸爸、老王的爸爸、老李的爸爸、老钱的爸爸、老周的爸爸,那么请问你是谁的儿子?

小伙伴:答,我谁也不是。(⊃・ᴥ・)つ

哈哈哈,是不是被整蒙啦,但今天可不是让他们继承你哈~

这次是来带你走进多继承的。


1.用“老赵”认识多继承

如果你有三个孩子,他们分别各自为家,那么关系图就像下面这样:

img

哈哈哈,请原谅我的联系线。---(∩┌┐∩)---

那么就可以说,赵小聪是赵小静和赵小白的儿子。(为什么呢,-因-为-出-轨-了-呗-~-(假装删除线))

假如说刚才加的那个删除线是真事,那么这就叫多继承。

现在呢,听懂了吧。

多继承就是两个妈妈的孩子。多个父对象的子对象。

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中的++效果一样。

结构像这样:

img

我们想它应该这样运行:

Grandpa called
Father1 called
Father2 called
Son called
[1, 1, 1, 1]

但他妈的跑成这样了:

img

???

怎么跑成两次了???

玩呢???!!!

img

骇,这他妈怎么回事,我不玩了。


好啦,这就是本期的内容!

散会~

img

(咳咳咳,这个表情包用几次了??)

好啦,竟然你翻到这里来了,就继续吧。

要知道,kzx是那种不讲情义的人吗~

3.1.MRO是什么鬼

要想开始,我们需要了解一个知识点:Method resolution order,简称MRO

???

MRO???什么鬼???

等一下,我来翻译一下:方法解析顺序。

MRO会在此方法左右干找找不到时,向前搜它的父亲、爷爷、曾祖、高祖……直至鼻祖(Python3默认会给所有的类增加一个父类,名为object,尤其表现在最前端的父类上,这就是新式类。而旧式类不会添加object)。

一般情况下,这个顺序有好多种情况:

  1. 从子类向前搜索。这种情况很简单,是单继承搜索方法,如:C->B->A
  2. 搜索子类的第一个父类,如果没有,则搜索父类的父类,直到搜索到最前端的父类(不包括object),开始照样搜索第二个父类,直到所有父类全部搜索完,如:C->B->Base->A->Base。这种方式为Depth first searchDFS,深度优先搜索) 。
  3. 使用DFS全部搜索,然后保留重复类中最后的类,如:C->B->A->Base。这种方法属于新式类。
  4. 使用merge()为搜索顺序排序。这种方式为C3,是至今唯一一个留存住的方式。

接下来,我们主要讲解C3的顺序。

3.2.C3???merge()???

很多人看到C3与merge(),都是一脸懵逼。

img

哈哈哈,别那么懵逼呀,我还没讲呢。(⊃・ᴥ・)つ

我们的C3公式,可记为:L[CLASS] = [CLASS] + merge(L(1),L[2],L[3],……,[1],[2],[3],……)

而计算出MRO顺序,可以:

  1. L[类] = [类] + merge(L[1],L[2],……,[1],[2],……)
  2. L[类] = [类] + merge([1,1和2共同的父类],[2,1和2共同的父类],[1和2共同的父类,object],[1,2])
  3. L[类] = [类,1] + merge([1和2共同的父类],[2,1和2共同的父类],[1和2共同的父类,object],[2])
  4. L[类] = [类,1,2] + merge([1和2共同的父类],[1和2共同的父类],[1和2共同的父类,object])
  5. L[类] = [类,1,2,1和2共同的父类] + merge([object])
  6. 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__,可以得出顺序:

img

怎么样。

而我们刚才那个恶心多继承,我们可以计算一下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]

验证:

img

现在知道了吧,它先执行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])

跑起来:

img

是不是很神奇,它把两次的super().call()合并起来了。


诶呦!这期严重超长了。

这就是本期的内容!

散会~

img

results matching ""

    No results matching ""