C3 线性化算法与 MRO之Python中的多继承

目录
  • 什么是 MRO
  • New-style Class vs. Old-style Class
  • 理解 old-style class 的 MRO
  • 理解 new-style class 的 MRO
  • C3线性化算法

Python 中的方法解析顺序(Method Resolution Order, MRO)定义了多继承存在时 Python 解释器查找函数解析的正确方式。当 Python 版本从 2.2 发展到 2.3 再到现在的 Python 3,MRO算法也随之发生了相应的变化。这种变化在很多时候影响了我们使用不同版本 Python 编程的过程。

什么是 MRO

MRO 全称方法解析顺序(Method Resolution Order)。它定义了 Python 中多继承存在的情况下,解释器查找函数解析的具体顺序。什么是函数解析顺序?我们首先用一个简单的例子来说明。请仔细看下面代码:

class A():
    def who_am_i(self):
        print("I am A")

class B(A):
    pass

class C(A):
    def who_am_i(self):
        print("I am C")

class D(B,C):
    pass

d = D()

如果我问在 Python 2 中使用 D 的实例调用 d.who_am_i(),究竟执行的是 A 中的 who_am_i() 还是 C 中的 who_am_i(),我想百分之九十以上的人都会不假思索地回答:肯定是 C 中的 who_am_i(),因为 C 是 D 的直接父类。然而,如果你把代码用 Python 2 运行一下就可以看到 d.who_am_i() 打印的是 I am A

是不是觉得很混乱很奇怪?感到奇怪就对了!!!

这个例子充分展示了 MRO 的作用:决定基类中的函数到底应该以什么样的顺序调用父类中的函数。可以明确地说,Python 发展到现在,MRO 算法已经不是一个凭借着执行结果就能猜出来的算法了。如果没有深入到 MRO 算法的细节,稍微复杂一点的继承关系和方法调用都能彻底绕晕你。

New-style Class vs. Old-style Class

在介绍不同版本的 MRO 算法之前,我们有必要简单地回顾一下 Python 中类定义方式的发展历史。尽管在 Python 3 中已经废除了老式的类定义方式和 MRO 算法,但对于仍然广泛使用的 Python 2 来说,不同的类定义方式与 MRO 算法之间具有紧密的联系。了解这一点将帮助我们从 Python 2 向 Python 3 迁移时不会出现莫名其妙的错误。

在 Python 2.1 及以前,我们定义一个类的时候往往是这个样子(我们把这种类称为 old-style class):

class A:
    def __init__(self):
        pass

Python 2.2 引入了新的模型对象(new-style class),其建议新的类型通过如下方式定义:

class A(object):
    def __init__(self):
        pass

注意后一种定义方式显示注明类 A 继承自 object。Python 2.3 及后续版本为了保持向下兼容,同时提供以上两种类定义用以区分 old-style class 和 new-style class。Python 3 则完全废弃了 old-style class 的概念,不论你通过以上哪种方式书写代码,Python 3 都将明确认为类 A 继承自 object。这里我们只是引入 old-style 和 new-style 的概念,如果你对他们的区别感兴趣,可以自行看 stackoverflow 上有关该问题的解释

理解 old-style class 的 MRO

我们使用前文中的类继承关系来介绍 Python 2 中针对 old-style class 的 MRO 算法。如果你在前面执行过那段代码,你可以看到调用 d.who_am_i() 打印的应该是 I am A。为什么 Python 2 的解释器在确定 D 中的函数调用时要先搜索 A 而不是先搜索 D 的直接父类 C 呢?

这是由于 Python 2 对于 old-style class 使用了非常简单的基于深度优先遍历的 MRO 算法(关于深度优先遍历,我想大家肯定都不陌生)。当一个类继承自多个类时,Python 2 按照从左到右的顺序深度遍历类的继承图,从而确定类中函数的调用顺序。这个过程具体如下:

  • 检查当前的类里面是否有该函数,如果有则直接调用。
  • 检查当前类的第一个父类里面是否有该函数,如果没有则检查父类的第一个父类是否有该函数,以此递归深度遍历。
  • 如果没有则回溯一层,检查下一个父类里面是否有该函数并按照 2 中的方式递归。

上面的过程与标准的深度优先遍历只有一点细微的差别:步骤 2 总是按照继承列表中类的先后顺序来选择分支的遍历顺序。具体来说,类 D 的继承列表中类顺序为 B, C,因此,类 D 按照先遍历 B 分支再遍历 C 分支的顺序来确定 MRO。

我们继续用第一个例子中的函数继承图来说明这个过程:

按照上述深度递归的方式,函数 d.who_am_i() 调用的搜索顺序是 D, B, A, C, A。由于一个类不能两次出现,因此在搜索路径中去除掉重复出现的 A,得到最终的方法解析顺序是 D, B, A, C。这样一来你就明白了为什么 d.who_am_i() 打印的是 I am A 了。

在 Python 2 中,我们可以通过如下方式来查看 old-style class 的 MRO:

>>> import inspect
>>> inspect.getmro(D)

理解 new-style class 的 MRO

从上面的结果可以看到,使用深度优先遍历的查找算法并不合理。因此,Python 3 以及 Python 2 针对 new-style class 采用了新的 MRO 算法。如果你使用 Python 3 重新运行一遍上述脚本,你就可以看到函数 d.who_am_i() 的打印结果是 I am C

>>> d.who_am_i()
I am C
>>> D.__mro__
(<class 'test.D'>, <class 'test.B'>, <class 'test.C'>, <class 'test.A'>, <class 'object'>)

新算法与基于深度遍历的算法类似,但是不同在于新算法会对深度优先遍历得到的搜索路径进行额外的检查。其从左到右扫描得到的搜索路径,对于每一个节点解释器都会判断该节点是不是好的节点。如果不是好的节点,那么将其从当前的搜索路径中移除。

那么问题在于,什么是一个好的节点?我们说 N 是一个好的节点当且仅当搜索路径中 N 之后的节点都不继承自 N。我们还以上述的类继承图为例,按照深度优先遍历得到类 D 中函数的搜索路径 D, B, A, C, A。之后 Python 解释器从左向右检查时发现第三个节点 A 不是一个好的节点,因为 A 之后的节点 C 继承自 A。因此其将 A 从搜索路径中移除,然后得到最后的调用顺序 D, B, C, A。

采用上述算法,D 中的函数调用将优先查找其直接父类 B 和 C 中的相应函数。

C3线性化算法

上一小结我们从直观上概述了针对 new-style class 的 MRO 算法过程。事实上这个算法有一个明确的名字 C3 linearization。下面我们给出其形式化的计算过程。

上面的过程看起来好像很复杂,我们用一个例子来具体执行一下,你就会觉得其实还是挺简单的。假设我们有如下的一个类继承关系:

class X():
    def who_am_i(self):
        print("I am a X")

class Y():
    def who_am_i(self):
        print("I am a Y")

class A(X, Y):
    def who_am_i(self):
        print("I am a A")

class B(Y, X):
     def who_am_i(self):
         print("I am a B")

class F(A, B):
    def who_am_i(self):
        print("I am a F")

Traceback (most recent call last):
  File "test.py", line 17, in <module>
    class F(A, B):
TypeError: Cannot create a consistent method resolution
order (MRO) for bases X, Y

参考文献 Python Tutorial: Understanding Python MRO - Class search path The Python 2.3 Method Resolution Order C3 linearization

到此这篇关于C3 线性化算法与 MRO之Python中的多继承的文章就介绍到这了,更多相关Python多继承内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Python多继承以及MRO顺序的使用

    多继承以及MRO顺序 1. 单独调用父类的方法 # coding=utf-8 print("******多继承使用类名.__init__ 发生的状态******") class Parent(object): def __init__(self, name): print('parent的init开始被调用') self.name = name print('parent的init结束被调用') class Son1(Parent): def __init__(self, name,

  • python多重继承新算法C3介绍

    mro即 method resolution order (方法解释顺序),主要用于在多继承时判断属性的路径(来自于哪个类). 在python2.2版本中,算法基本思想是根据每个祖先类的继承结构,编译出一张列表,包括搜索到的类,按策略删除重复的.但是,在维护单调性方面失败过(顺序保存),所以从2.3版本,采用了新算法C3. 为什么采用C3算法 C3算法最早被提出是用于Lisp的,应用在Python中是为了解决原来基于深度优先搜索算法不满足本地优先级,和单调性的问题. 本地优先级:指声明时父类的顺

  • 浅谈Python的方法解析顺序(MRO)

    方法解析顺序, Method Resolution Order 从一段代码开始 考虑下面的情况: class A(object): def foo(self): print('A.foo()') class B(object): def foo(self): print('B.foo()') class C(B, A): pass c = C() c.foo() C同时继承了类A和类B, 它们都有各自的foo()方法. 那么C的实例c调用foo()方法时, 到底是调用A.foo()还是B.foo

  • python支持多继承吗

    大部分面向对象的编程语言(除了C++)都只支持单继承,而不支持多继承,为什么呢?因为多继承不仅增加编程复杂度,而且容易导致莫名其妙的错误. Python虽然语法上支持多继承,但是却不推荐使用多继承,而是推荐使用单继承,这样可以保证编程思路更清晰,也可以避免不必要的麻烦. 当以一个子类有多个直接父类时,该子类会继承得到所有父类的方法,但是如果其中有多个父类包含同名方法会发生什么?此时排在前面的父类中的方法会"遮蔽"后面父类中的方法. 栗子如下: #coding=utf-8 class I

  • 在python里面运用多继承方法详解

    如何在PYTHON里面运用多继承 class Father: def hobby(self): print("love to play video game.") class Mother: def cook(self): print("love to cook anything.") #比如说有两个类,如果想要一个子类同时继承这两个类,应该怎么操作呢. class Father: def hobby(self): print("love to play

  • C3 线性化算法与 MRO之Python中的多继承

    目录 什么是 MRO New-style Class vs. Old-style Class 理解 old-style class 的 MRO 理解 new-style class 的 MRO C3线性化算法 Python 中的方法解析顺序(Method Resolution Order, MRO)定义了多继承存在时 Python 解释器查找函数解析的正确方式.当 Python 版本从 2.2 发展到 2.3 再到现在的 Python 3,MRO算法也随之发生了相应的变化.这种变化在很多时候影响

  • Python中的单继承与多继承实例分析

    本文实例讲述了Python中的单继承与多继承.分享给大家供大家参考,具体如下: 单继承 一.介绍 Python 同样支持类的继承,如果一种语言不支持继承,类就没有什么意义.派生类的定义如下所示: class DerivedClassName(BaseClassName1): <statement-1> . . . <statement-N> 需要注意圆括号中基类的顺序,若是基类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找基类

  • 浅谈Python中重载isinstance继承关系的问题

    判断继承关系 通过内建方法 isinstance(object, classinfo) 可以判断一个对象是否是某个类的实例.这个关系可以是直接,间接或抽象. 实例的检查是允许重载的,可见文档customizing-instance-and-subclass-checks.根据 PEP 3119的描述: The primary mechanism proposed here is to allow overloading the built-in functions isinstance() an

  • python中super()函数的理解与基本使用

    目录 前言 super的用法 super的原理 Python super()使用注意事项 混用super与显式类调用 不同种类的参数 总结 前言 Python是一门面向对象的语言,定义类时经常要用到继承,在类的继承中,子类继承父类中已经封装好的方法,不需要再次编写,如果子类如果重新定义了父类的某一方法,那么该方法就会覆盖父类的同名方法,但是有时我们希望子类保持父类方法的基础上进行扩展,而不是直接覆盖,就需要先调用父类的方法,然后再进行功能的扩展,这时就可以通过super来实现对父类方法的调用.

  • python中Apriori算法实现讲解

    本文主要给大家讲解了Apriori算法的基础知识以及Apriori算法python中的实现过程,以下是所有内容: 1. Apriori算法简介 Apriori算法是挖掘布尔关联规则频繁项集的算法.Apriori算法利用频繁项集性质的先验知识,通过逐层搜索的迭代方法,即将K-项集用于探察(k+1)项集,来穷尽数据集中的所有频繁项集.先找到频繁项集1-项集集合L1, 然后用L1找到频繁2-项集集合L2,接着用L2找L3,知道找不到频繁K-项集,找到每个Lk需要一次数据库扫描.注意:频繁项集的所有非空

  • python中实现k-means聚类算法详解

    算法优缺点: 优点:容易实现 缺点:可能收敛到局部最小值,在大规模数据集上收敛较慢 使用数据类型:数值型数据 算法思想 k-means算法实际上就是通过计算不同样本间的距离来判断他们的相近关系的,相近的就会放到同一个类别中去. 1.首先我们需要选择一个k值,也就是我们希望把数据分成多少类,这里k值的选择对结果的影响很大,Ng的课说的选择方法有两种一种是elbow method,简单的说就是根据聚类的结果和k的函数关系判断k为多少的时候效果最好.另一种则是根据具体的需求确定,比如说进行衬衫尺寸的聚

  • 详解K-means算法在Python中的实现

    K-means算法简介 K-means是机器学习中一个比较常用的算法,属于无监督学习算法,其常被用于数据的聚类,只需为它指定簇的数量即可自动将数据聚合到多类中,相同簇中的数据相似度较高,不同簇中数据相似度较低. K-MEANS算法是输入聚类个数k,以及包含 n个数据对象的数据库,输出满足方差最小标准k个聚类的一种算法.k-means 算法接受输入量 k :然后将n个数据对象划分为 k个聚类以便使得所获得的聚类满足:同一聚类中的对象相似度较高:而不同聚类中的对象相似度较小. 核心思想 通过迭代寻找

  • python中PS 图像调整算法原理之亮度调整

    亮度调整 非线性亮度调整: 对于R,G,B三个通道,每个通道增加相同的增量. 线性亮度调整: 利用HSL颜色空间,通过只对其L(亮度)部分调整,可达到图像亮度的线性调整.但是,RGB和HSL颜色空间的转换很繁琐,一般还需要浮点数的运算,不仅增加了代码的复杂度,更重要的是要逐点将RGB转换为HSL,然后确定新的L值,再将HSL转换为RGB,运行速度可想而知是很慢的.要想提高图像亮度线性调整的速度,应该从三方面考虑,一是变浮点运算为整数运算,二是只提取HSL的L部分进行调整,三是采用汇编代码,在De

  • python中K-means算法基础知识点

    能够学习和掌握编程,最好的学习方式,就是去掌握基本的使用技巧,再多的概念意义,总归都是为了使用服务的,K-means算法又叫K-均值算法,是非监督学习中的聚类算法.主要有三个元素,其中N是元素个数,x表示元素,c(j)表示第j簇的质心,下面就使用方式给大家简单介绍实例使用. K-Means算法进行聚类分析 km = KMeans(n_clusters = 3) km.fit(X) centers = km.cluster_centers_ print(centers) 三个簇的中心点坐标为: [

  • python中超简单的字符分割算法记录(车牌识别、仪表识别等)

    背景 在诸如车牌识别,数字仪表识别等问题中,最关键的就是将单个的字符分割开来再分别进行识别,如下图.最近刚好用到,就自己写了一个简单地算法进行字符分割,来记录一下. 图像预处理 彩图二值化以减小参数量,再进行腐蚀膨胀去除噪点. image = cv2.imread('F://demo.jpg', 0) # 读取为灰度图 _, image = cv2.threshold(image, 50, 255, cv2.THRESH_BINARY) # 二值化 kernel1 = cv2.getStruct

随机推荐