前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python多继承及MRO顺序

Python多继承及MRO顺序

作者头像
ZackSock
发布2021-04-13 15:12:25
3160
发布2021-04-13 15:12:25
举报
文章被收录于专栏:ZackSockZackSock

多继承的实现

代码语言:javascript
复制
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()

运行结果:

代码语言:javascript
复制
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
A类方法

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

代码语言:javascript
复制
class C(B, A):
    pass

结果就是

代码语言:javascript
复制
(<class ‘main.C’>, <class ‘main.B’>, <class ‘object’>, <class ‘main.A’>)
B类方法

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

代码语言:javascript
复制
class C(B, A):
    
    def out(self):
        print("C类方法")

结果如下:

代码语言:javascript
复制
(<class ‘main.C’>, <class ‘main.B’>, <class ‘object’>, <class ‘main.A’>)
C类方法

了解MRO

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

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

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

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

新式类和旧式类

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

代码语言:javascript
复制
class oldA:  
 pass

class oldB:
    pass

其中,oldAoldB 都属于旧式类

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

代码语言:javascript
复制
class NewA(object):  
    pass

class NewB(NewA):  
    pass

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

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

super()的使用

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

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

单继承

父类名调用
代码语言:javascript
复制
"""
单继承使用父类名调用
"""

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()

运行结果

代码语言:javascript
复制
son1 --- eat()
 parent --- 爱吃饭
 son1  ---  爱吃蔬菜

son2 --- eat()
 parent --- 爱吃饭
 son2  ---  爱吃水果
使用super()
代码语言:javascript
复制
"""
单继承中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()

运行结果:

代码语言:javascript
复制
son1 --- eat()
 parent --- 爱吃饭
 son1  ---  爱吃蔬菜

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

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

多继承

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

父类名调用
代码语言:javascript
复制
"""
多继承中父类名的使用
"""

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()

运行结果

代码语言:javascript
复制
grandson --- eat()
son1 --- eat()
 parent --- 爱吃饭
 son1  ---  爱吃蔬菜

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

 grandson --- 爱吃零食

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

使用super()
代码语言:javascript
复制
"""
多继承中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()

运行结果

代码语言:javascript
复制
grandson --- eat()
son1 --- eat()
son2 --- eat()
 parent --- 爱吃饭
 son2  ---  爱吃水果

 son1  ---  爱吃蔬菜

 grandson --- 爱吃零食

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

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

父类名调用

代码语言:javascript
复制
"""
父类名的使用
"""

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()

结果如下

代码语言:javascript
复制
[<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]

super()调用

代码语言:javascript
复制
"""
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()

结果如下

代码语言:javascript
复制
[<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()。结果如下

代码语言:javascript
复制
[<class '__main__.Grandson'>, 
 <class '__main__.Son1'>, 
 <class '__main__.Son2'>, 
 <class '__main__.Parent'>, 
 <class 'object'>
]
grandson --- eat()
 parent --- 爱吃饭
 grandson --- 爱吃零食

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

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

总结

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

公众号

新建文件夹X

大自然用数百亿年创造出我们现实世界,而程序员用几百年创造出一个完全不同的虚拟世界。我们用键盘敲出一砖一瓦,用大脑构建一切。人们把1000视为权威,我们反其道行之,捍卫1024的地位。我们不是键盘侠,我们只是平凡世界中不凡的缔造者 。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-04-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 新建文件夹X 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 多继承的实现
  • 了解MRO
  • 新式类和旧式类
  • super()的使用
    • 单继承
      • 父类名调用
      • 使用super()
    • 多继承
      • 父类名调用
      • 使用super()
  • 总结
  • 公众号
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档