Python多继承及MRO顺序

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Python多继承及MRO顺序

多继承的实现

class A(object):

    def out(self):
        print("A类方法")

class B(object):

    def out(self):
        print("B类方法")

class C(A, B):
    pass

c = C()
# 打印C类的调用路径顺序(注意要类名.__mro__)
print(C.__mro__)
c.out()

<br/>

运行结果:

(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
A类方法

<br/>

可以尝试一下把 C类 的继承顺序改成 B,A

class C(B, A):
    pass

结果就是

(<class ‘main.C’>, <class ‘main.B’>, <class ‘object’>, <class ‘main.A’>)
B类方法

<br/>

如果 C类out() 方法重写那么将执行 C类的 out() 方法

class C(B, A):
    
    def out(self):
        print("C类方法")

结果如下:

(<class ‘main.C’>, <class ‘main.B’>, <class ‘object’>, <class ‘main.A’>)
C类方法

<br/>

了解MRO

新式类可以直接通过 类名.__mro__ 的方式获取类的 MRO,也可以通过 类名.mro() 的形式,旧式类是没有 mro 属性和 mro() 方法的。

方法解析顺序 Method Resolution Order,简称 MRO。主要用于在多继承时判断方法,属性的调用路径。

  • 在搜索方法时,是按照 mro() 输出的结果,从左到右的顺序查找的
  • 如果找到,在当前类中找到方法就直接执行,不在搜索
  • 没有找到,就依次查找下一个类中是否有对应的方法,找到执行,不在搜索
  • 如果最后一个类,还没有找到方法,程序报错

MRO 的顺序是根据 Python中C3算法 得来的大家感兴趣可以去研究一下,这里就不在赘述了。

<br/>

新式类和旧式类

在早期版本的 Python 中,所有类并没有一个共同的祖先 object,如果定义一个类,但没有显式指定其祖先,那么就被解释为 旧式类,例如:

class oldA:  
    pass

class oldB:
    pass

其中,oldAoldB 都属于旧式类

Python 2.x 版本中,为了向后兼容保留了旧式类。该版本中的 新式类必须 显式继承 object 或者其他新式类:

class NewA(object):  
    pass

class NewB(NewA):  
    pass

显然,以上两个类都属于 新式类

而在 Python 3.x 版本中,不再保留旧式类的概念。因此,没有继承任何其他类的类都隐式地继承自 object

<br/>

super()的使用

super() 函数是用于调用父类(超类)的一个方法。

super() 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。

<br/>

单继承

父类名调用

"""
单继承使用父类名调用
"""

class Parent(object):

    def eat(self):
        print("\tparent --- 爱吃饭")


class Son1(Parent):


    def eat(self):
        print("son1 --- eat()")
        Parent.eat(self)
        print("\tson1  ---  爱吃蔬菜\n")
    

class Son2(Parent):


    def eat(self):
        print("son2 --- eat()")
        Parent.eat(self)
        print("\tson2  ---  爱吃水果\n")


def main():
    s1 = Son1()
    s2 = Son2()

    s1.eat()
    s2.eat()

    
if __name__ == '__main__':
    main()

<br/>

运行结果

son1 --- eat()
    parent --- 爱吃饭
    son1  ---  爱吃蔬菜

son2 --- eat()
    parent --- 爱吃饭
    son2  ---  爱吃水果

<br/>

使用super()

"""
单继承中super()的使用
"""

class Parent(object):

    def eat(self):
        print("\tparent --- 爱吃饭")


class Son1(Parent):


    def eat(self):
        print("son1 --- eat()")
        super().eat()
        print("\tson1  ---  爱吃蔬菜\n")


class Son2(Parent):


    def eat(self):
        print("son2 --- eat()")
        super().eat()
        print("\tson2  ---  爱吃水果\n")


def main():
    s1 = Son1()
    s2 = Son2()

    s1.eat()
    s2.eat()


if __name__ == '__main__':
    main()

<br/>

运行结果:

son1 --- eat()
    parent --- 爱吃饭
    son1  ---  爱吃蔬菜

son2 --- eat()
    parent --- 爱吃饭
    son2  ---  爱吃水果

<br/>

可以发现在单继承使用 父类名super() 调用父类方法结果都一样。

<br/>

多继承

还是上面例子,就是加一个 GrandSon 类,让它继承 Son1, Son2 。让 eat() 具备其父类的特性。

父类名调用

"""
多继承中父类名的使用
"""

class Parent(object):

    def eat(self):
        print("\tparent --- 爱吃饭")


class Son1(Parent):


    def eat(self):
        print("son1 --- eat()")
        Parent.eat(self)
        print("\tson1  ---  爱吃蔬菜\n")
    

class Son2(Parent):


    def eat(self):
        print("son2 --- eat()")
        Parent.eat(self)
        print("\tson2  ---  爱吃水果\n")


class Grandson(Son1, Son2):
    
    def eat(self):
        print("grandson --- eat()")
        # super().eat()
        Son1.eat(self)
        Son2.eat(self)
        print("\tgrandson --- 爱吃零食")


def main():
    # s1 = Son1()
    # s2 = Son2()

    # s1.eat()
    # s2.eat()

    g = Grandson()
    g.eat()


if __name__ == '__main__':
    main()

<br/>

运行结果

grandson --- eat()
son1 --- eat()
    parent --- 爱吃饭
    son1  ---  爱吃蔬菜

son2 --- eat()
    parent --- 爱吃饭
    son2  ---  爱吃水果

    grandson --- 爱吃零食

结果显示 Parenteat() 方法调用了多次,存在重复调用。

<br/>

使用super()

"""
多继承中super()的使用
"""

class Parent(object):

    def eat(self):
        print("\tparent --- 爱吃饭")


class Son1(Parent):


    def eat(self):
        print("son1 --- eat()")
        super().eat()
        print("\tson1  ---  爱吃蔬菜\n")


class Son2(Parent):


    def eat(self):
        print("son2 --- eat()")
        super().eat()
        print("\tson2  ---  爱吃水果\n")


class Grandson(Son1, Son2):
    
    def eat(self):
        print("grandson --- eat()")
        super().eat()
        print("\tgrandson --- 爱吃零食")


def main():
    g = Grandson()
    g.eat()


if __name__ == '__main__':
    main()

<br/>

运行结果

grandson --- eat()
son1 --- eat()
son2 --- eat()
    parent --- 爱吃饭
    son2  ---  爱吃水果

    son1  ---  爱吃蔬菜

    grandson --- 爱吃零食

可以发现在多继承中使用 super() 没有重复调用。

<br/>

假如在多继承中 Grandson 类的 eat() 方法只想复用 Parent, Son1eat()的方法,不需要 Son2的。该如何实现呢?

父类名调用

"""
父类名的使用
"""

class Parent(object):

    def eat(self):
        print("\tparent --- 爱吃饭")


class Son1(Parent):

    def eat(self):
        print("son1 --- eat()")
        Parent.eat(self)
        print("\tson1  ---  爱吃蔬菜\n")
    

class Son2(Parent):

    def eat(self):
        print("son2 --- eat()")
        Parent.eat(self)
        print("\tson2  ---  爱吃水果\n")


class Grandson(Son1, Son2):
    
    def eat(self):
        print("grandson --- eat()")
        Son1.eat(self)
        # Son2.eat(self)
        print("\tgrandson --- 爱吃零食")


def main():
    print(Grandson.mro())
    g = Grandson()
    g.eat()


if __name__ == '__main__':
    main()

<br/>

结果如下

[<class '__main__.Grandson'>, 
 <class '__main__.Son1'>, 
 <class '__main__.Son2'>, 
 <class '__main__.Parent'>, 
 <class 'object'>
]

grandson --- eat()
son1 --- eat()
    parent --- 爱吃饭
    son1  ---  爱吃蔬菜

    grandson --- 爱吃零食
[Finished in 0.1s]

<br/>

super()调用

"""
super()的使用
"""

class Parent(object):

    def eat(self):
        print("\tparent --- 爱吃饭")


class Son1(Parent):

    def eat(self):
        print("son1 --- eat()")
        super().eat()
        print("\tson1  ---  爱吃蔬菜\n")


class Son2(Parent):

    def eat(self):
        print("son2 --- eat()")
        super().eat()
        print("\tson2  ---  爱吃水果\n")


class Grandson(Son1, Son2):
    
    def eat(self):
        print("grandson --- eat()")
        super(Son1, self).eat()
        # Son1.eat(self)
        # Son2.eat(self)
        print("\tgrandson --- 爱吃零食")


def main():
    print(Grandson.mro())
    g = Grandson()
    g.eat()


if __name__ == '__main__':
    main()

<br/>

结果如下

[<class '__main__.Grandson'>, 
 <class '__main__.Son1'>, 
 <class '__main__.Son2'>, 
 <class '__main__.Parent'>, 
 <class 'object'>
]

grandson --- eat()
son2 --- eat()
    parent --- 爱吃饭
    son2  ---  爱吃水果

    grandson --- 爱吃零食
[Finished in 0.1s]

然而却发现 super(Son1, self).eat() 调用的是 Son2eat() 方法。

是因为 MRO 的原因,当调用 super(Son1,self).eat() 时 ,会拿 Son1GrandsonMRO方法解析顺序表 中寻找,找到然后 super() 调用则是列表中下一个,这里是 Son2,然后 Son2.eat() 中使用了 super().eat(),此时是拿其本身 Son2Son2MRO方法解析顺序表 中寻找,然后 super().eat() 调用,则是列表的下一个 Parent.eat()

因此在 Grandsonsuper(Son1, self).eat() 调用的是 Son2.eat()

假如 super(Son2, self).eat() 调用的则是 Parent.eat()。结果如下

[<class '__main__.Grandson'>, 
 <class '__main__.Son1'>, 
 <class '__main__.Son2'>, 
 <class '__main__.Parent'>, 
 <class 'object'>
]
grandson --- eat()
    parent --- 爱吃饭
    grandson --- 爱吃零食

因此只能使用 父类名 的形式调用 Son1.eat()只继承 Son1 的特性。如果不清楚 super()调用的谁,打印其

类名.mro(),对照 MRO方法解析顺序表,就一目了然。

<br/>

总结

  • 方法解析顺序 Method Resolution Order,简称 MRO。主要用于在多继承时判断方法,属性的调用路径
  • 旧式类,没有共同的 object祖先且没有显式的指定其祖先。
  • 新式类,在 Python 2.x 版本中显式继承 object 或者其他新式类,Python3.x中则是隐式继承object
  • super().method() 相对于 类名.method(self),在 单继承 上用法基本无差
  • 多继承 上有区别,super() 方法 能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次,具体看前面的输出结果

<br/>

源代码

源代码已上传到 Gitee PythonKnowledge: Python知识宝库,欢迎大家来访。

✍ 码字不易,还望各位大侠多多支持❤️。

<br/>

公众号

新建文件夹X

大自然用数百亿年创造出我们现实世界,而程序员用几百年创造出一个完全不同的虚拟世界。我们用键盘敲出一砖一瓦,用大脑构建一切。人们把1000视为权威,我们反其道行之,捍卫1024的地位。我们不是键盘侠,我们只是平凡世界中不凡的缔造者 。
相关文章
|
15天前
|
算法 Python
Python多继承时子类如何调用指定父类
通过本文的介绍,希望您能够深入理解Python多继承时子类如何调用指定父类的方法,并在实际项目中灵活运用这些技巧,编写出高效且易维护的代码。
24 11
|
14天前
|
算法 Python
Python多继承时子类如何调用指定父类
通过本文的介绍,希望您能够深入理解Python多继承时子类如何调用指定父类的方法,并在实际项目中灵活运用这些技巧,编写出高效且易维护的代码。
31 1
|
4月前
|
算法 Python
python多继承的3C算法是什么?怎么用?
有很多地方都说python多继承的继承顺序,是按照深度遍历的方式,其实python多继承顺序的算法,不是严格意义上的深度遍历,而是基于深度遍历基础上优化出一种叫3C算法
|
4月前
|
Python
Python 中的不同类型继承
【8月更文挑战第24天】
59 0
|
4月前
|
算法 Python
Python 中的继承:强大的代码复用机制
【8月更文挑战第23天】
69 0
|
6月前
|
Python
Python面向对象进阶:深入解析面向对象三要素——封装、继承与多态
Python面向对象进阶:深入解析面向对象三要素——封装、继承与多态
|
7月前
|
Python
Python进阶---面向对象 继承
Python进阶---面向对象 继承
31 1
|
7月前
|
Python
Python 继承
Python 继承
41 0
|
7月前
|
Python
Python从入门到精通:深入学习面向对象编程——2.1.2继承、封装和多态的概念
Python从入门到精通:深入学习面向对象编程——2.1.2继承、封装和多态的概念
|
7月前
|
数据库 Python
python面型对象编程进阶(继承、多态、私有化、异常捕获、类属性和类方法)(下)
python面型对象编程进阶(继承、多态、私有化、异常捕获、类属性和类方法)
94 0