前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Python 方法解析顺序(MRO)

Python 方法解析顺序(MRO)

作者头像
不可言诉的深渊
发布2021-02-07 16:28:15
发布2021-02-07 16:28:15
73100
代码可运行
举报
运行总次数:0
代码可运行

前几天我在 B 站录制《Python 基础教程》(第 3 版)演示视频,我说到 Python 一个子类同时继承多个父类的时候,如果多个父类有同名方法,子类应该调用哪一个父类的同名方法,这取决于子类查找多个父类的方法的顺序,我们把这个顺序称之为方法解析顺序(MRO),MRO 的实现算法非常的复杂,效果也很好,虽然书上说不需要为此担心,但是还是需要讲一下这个顺序,不然可能会得不到你想要的结果。

为什么不去 B 站讲这个

有些人可能会问,为什么我不去 B 站(我的 B 站昵称文末会给出)讲,偏要在微信公众号说这个,因为我的 B 站针对的是 0 基础的人,而这个 MRO 算法确实非常复杂,其本质就是图这种数据结构的一个应用,我猜测我讲了没多少人听得懂。而微信公众号可不一样,因为已经有两大开发者社区在同步我的微信公众号文章,这两大开发者社区中大多数都是技术牛人,所以我选择先让看我文章的技术牛人先学会这个,因此现在微信公众号讲一下。逛 B 站的菜鸟们,你们尽管放心,这个问题已经收录在我的遗留问题里面了,我并没有抛弃你们,后面我会在 B 站讲这个方法解析顺序(当然讲之前会给你们补计算机基础知识)。当然,即使书上没有提及这个东西,我后面也还是会给你们补计算机基础知识,因为从第 10 章第 3 节开始就已经开始涉及计算机基础知识了,如果你没有计算机基础知识这一节你自己看书你有 70% 的内容可能看不懂,特别是第 4 小节和第 7 小节。

菱形继承

我们先来看一下比较简单的菱形继承对应的 UML 类图,如图所示。

接下来再看一下这个类图对应的代码,这里我只定义了三个类,object 被隐式继承,所以不作声明。

代码语言:javascript
代码运行次数:0
运行
复制
from pprint import pprint

class V1:    pass

class V2:    pass

class V3(V1, V2):    pass

pprint(V3.mro())

大家也都看到了,查看方法解析顺序直接用类调用 mro 方法并输出即可,我们来看看输出的结果,如图所示。

我们可以发现顺序是 V3,V2,V1,object。如果把类图抽象成数据结构的有向图,这有点像广度优先遍历,同时也有点像拓扑排序。具体是哪一个现在还是看不出来的,我们来把类图变一下,如图所示。

其对应的代码如下所示。

代码语言:javascript
代码运行次数:0
运行
复制
from pprint import pprint

class V1:    pass

class V2(V1):    pass

class V3(V1, V2):    pass

pprint(V3.mro())

运行一下看看,如图所示。

我们可以发现对方不想和你说话,并向你抛出了一个异常。那么这是不是意味着它就没有办法实现之前的 UML 类图呢?其实还是有办法的,非常的简单,我们把类 V3 继承的两个类顺序换一下就行了,类图还是之前的类图,代码也就 V3 需要修改,V1 和 V2 不需要修改,所以我只给出 V3 的代码,如下所示。

代码语言:javascript
代码运行次数:0
运行
复制
class V3(V2, V1):    pass

我们修改好之后继续运行一下看看,如图所示。

我们还是看不出来是广度优先遍历还是拓扑排序中的哪一个,但是我们可以得出一个非常重要的结论:继承多个类的时候,我们需要把具体的类放在前面,抽象的类放在后面。在这里 V2 继承了 V1,所以 V2 比 V1 更具体,因此 V3 继承这两个类需要先写 V2 后写 V1。我们或许早就发现了,上面的继承有一些多余,V3 只继承 V2 和继承 V2 和 V1 难道不是一回事吗?确实是一回事,但是我必须用这种方法来找出这个顺序究竟是广度优先遍历还是拓扑排序,下面我们来看一个更复杂的例子,总共有 10 个类。

更复杂的例子

在这里我们来看一个更复杂的例子,先上 UML 类图。

其对应的代码如下所示。

代码语言:javascript
代码运行次数:0
运行
复制
from pprint import pprint

class V1:    pass

class V2:    pass

class V3(V1, V2):    pass

class V4(V3):    pass

class V5(V3):    pass

class V6(V3):    pass

class V7(V4, V5, V6):    pass

class V8(V7):    pass

class V9(V7):    pass

class V10(V8, V9, V1):    pass

pprint(V10.mro())

运行一下可以发现结果如下图所示。

我们可以发现,其顺序绝对不可能是广度优先遍历,因为如果是广度优先遍历,访问完 V8 和 V9 之后就要访问 V1,而不是访问 V7。那么拓扑排序对不对呢?其实可以带进去看,拓扑排序是对的。但是这样的图对应的拓扑排序可不止一个,为什么多次运行结果一样?这个原因非常简单,我们可以发现,V10 之后可以是 V8,V9 这样的顺序,也可以是 V9,V8 这样的顺序,为什么每次都是 V8,V9 这个顺序呢?大家去看一下 V10 这个类的定义就知道了,因为 V10 继承三个类,即 V8,V9 和 V1。而且我上面写的顺序也是 V8,V9,V1。所以顺序只可能是 V8,V9(V1 这个多余的我们不去管它)这样的顺序,不可能是 V9,V8 这样的顺序。

总结

通过上面对 Python 方法解析顺序(MRO)的讲解,我们可以得出以下两个结论:

  1. 继承多个类的时候要把越具体的类写在越前面,越抽象的类写在越后面。
  2. 方法解析顺序就是拓扑排序外加一件事:先写先排。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-02-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Python机器学习算法说书人 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档