Python学习笔记--面向对象编程基础知识(多重继承与MRO)

本文摘自朱雷老师所著《Python工匠》一书第9章内容,因为很多内容,阅读时依然一知半解,特做笔记加强认知,及后续加强理解。

本文通过几段代码,几个简单类继承关系,重点说明和理解如下一句话:

在许多人的印象中,super()是一个调用父类方法的工具函数,但这么说并不准确,super()使用的其实不是当前类的父类,而是它在MRO链条里的上一个类。

许多编程语言在处理继承关系时,只允许子类继承一个父类,而Python里的一个类可以同时继承多个父类。这让我们的模型设计变得更灵活,但同时也带来一个新的问题:“在复杂的继承关系下,如果确认子类的某个方法会用到哪个父类?”

以下面的代码为例:

class A:
    def say(self):
        print("I'm A")

class B(A):
    pass

class C(A):
    def say(self):
        print("I'm C")

class D(B, C):
    pass

D同时继承B和C两个父类,而B和C都是A的子类。如果调用D类实例的say()方法,系统是调用A类的say()方法输出“I ' m A”,还是调用C类的say()方法输出“I ' m C”呢?答案是:

>>>D().say()

I ' m C

这是因为,在解决多重继承的方法的优先级问题时,Python使用了一种名为MRO(method resolution order)的算法。该算法会遍历类的所有基类,并将它们按照优先级从高到低排序。

调用类的mro()方法,可以看到按照MRO算法排好序的基类列表:

>>>D.mro()

[, , , , ]

故调用D.say(),首先从D类中寻找,然后B类中寻找,再然后从类中寻找,再然后从A类中寻找,最先找到,优先调用。上面是每个Python类的默认基类。

上面代码的类继承B和C类,D类中没有定义say()方法,B中也没有定义say()方法,C类中定义了say()方法,最先找到,所以被调用,输出I ' m C”。

如果B类中也定义了say()方法如下:

class A:
    def say(self):
        print("I'm A")

class B(A):
    pass
    def say(self):
        print("I'm B")

class C(A):
    def say(self):
        print("I'm C")

class D(B, C):
    pass

>>>D().say()

I ' m B

如果B和C类都没有定义say()方法,则因为B和C都继承了A类,则调用A类的say()方法,会输出“I ' m A”

当然,如果D类中也重新定义了say()方法,则优先调用D类中定义的say()方法。

故,Python中,当调用子类的某个方法时,Python会按照MRO列表,从前向后寻找这个方法,假如某个类实现了这个方法,就直接调用返回。

MRO与super()

基于MRO算法的基类优先级列表,不光定义了类方法的找寻顺序,还影响了另一个常见的内置函数:super()。

在许多人的印象中,super()是一个调用父类方法的工具函数,但这么说并不准确,super()使用的其实不是当前类的父类,而是它在MRO链条里的上一个类。

举个例子:

class A:
    def __init__(self) -> None:
        print('I am A')
        super().__init__()

class B(A):
    def __init__(self) -> None:
        print('I am B')
        super().__init__()

class D1(B):
    pass

>>>D1()   # 实例化D1类,输出如下:

'I am B'

'I am A'

在上面的单一继承关系下,实例化D1类,D1类中没有定义实例化__init__()方法,则调用继承的B类的实例化__init__()方法,先执行print(' I am B') ,因为B继承了A,B类中执行super().__init__()方法,则又输出'I am A'

如果D类中也定义了__init__()方法,如下:

class A:
    def __init__(self) -> None:
        print('I am A')
        super().__init__()

class B(A):
    def __init__(self) -> None:
        print('I am B')
        super().__init__()

class D1(B):
    def __init__(self) -> None:
        print('I am D')
        super().__init__()

>>>D1()   # 实例化D1类,输出如下:

'I am D'

'I am B'

'I am A'

以上比较好理解,super()看上去就像是在调用父类的方法。但是,如果稍微调整一下继承关系,把C类加入到继承关系链中,D2类继承B和C类:

class A:
    def __init__(self) -> None:
        print('I am A')
        super().__init__()

class B(A):
    def __init__(self) -> None:
        print('I am B')
        super().__init__()

class C(A):
    def __init__(self) -> None:
        print('I am C')
        super().__init__()


class D1(B):
    def __init__(self) -> None:
        print('I am D')
        super().__init__()

class D2(B,C):
    pass

>>>D2()   # 实例化D2类,输出如下:

'I am B'

'I am C'

'I am A'

上面代码,D2类按照顺序继承B和C,D2类中没有定义实例化__init__()方法,对D2类实例化:

1)首先执行B类的__init__()方法,先执行print('I am B')输出'I am B'

2)然后执行super().__init__()方法,B类继承了A类,但是执行的是C类中的__init__()方法,先执行了print('I am C')输出'I am C',然后执行C类中的super().__init__()方法

3)C类继承了A类,执行了print('I am A')输出'I am A'

当我们在继承关系中加入C类后,B.__init__()里的super()不会再直接找到B的父类A,而是会定位到当前MRO链条里的下一个类,一个看上去和B毫不相关的类:C。

大多数情况下,可能需要的并不是多重继承,也许只是一个更准确的抽象模型,在该模型下,最普通的继承关系就能完美解决问题。

你可能感兴趣的:(python,学习,笔记)