Method Resolution Order – Python类的方法解析顺序

时间:2022-05-15 22:37:15

在支持多重继承的编程语言中,查找方法具体来自那个类时的基类搜索顺序通常被称为方法解析顺序(Method Resolution Order),简称MRO。(Python中查找其它属性也遵循同一规则。)对于只支持单重继承的语言,MRO十分简单;但是当考虑多重继承的情况时,MRO算法的选择非常微妙。Python先后出现三种不同的MRO:经典方式、Python2.2 新式算法、Python2.3 新式算法(也称作C3)。Python 3中只保留了最后一种,即C3算法。

经典类采用了一种简单MRO机制:查找一个方法时,搜索基类简单的深度优先从左到右。搜索过程中第一个匹配的对象作为返回结果。例如,考虑如下类:

class A:
def save(self): pass class B(A): pass class C:
def save(self): pass class D(B, C): pass

对于类D的实例x,它的经典MRO结果是类D,B,A,C顺序。因此查找方法x.save()将会得到A.save()(而不是C.save())。这个机制对于简单情况工作良好,但是对于更复杂的多重继承关系,暴露出的问题就比较明显了。其中问题之一是关于”菱形继承”时的方法查找顺序。例如:

class A:
def save(self): pass class B(A): pass class C(A):
def save(self): pass class D(B, C): pass

此处类D继承自类B和类C,类B和类C均继承自类A。应用传统的MRO,查找方法时在类中的搜索顺序是D, B, A, C, A。因此,语句x.save()将会如前一样调用A.save()。然而,这可能非你所需!既然B和C都从A继承,别人可以争辩说重新定义的方法C.save()可以被看做是比类A中的方法”更具体”(实际上,有可能C.save()会调用A.save()),所以C.save()才是你应该调用的。如果save方法是用于保持对象的状态,不调用C.save()将使C的状态被丢弃,进而程序出错。

虽然当时的Python代码很少存在这种多重继承代码,”新类”(new-style class)的出现则使之成为常见现象。这是由于所有的新类都继承自object这一基类。因此涉及到新类的多重继承总是会产生前面所述的菱形关系。例如:

class B(object): pass

class C(object):
def __setattr__(self, name, value): pass class D(B, C): pass

而且,object定义了一些方法(例如__setattr__())可以由子类扩展,这时解析顺序更是至关重要。上面代码所属例子中,方法C.__setattr__应当被应用到类D的实例。

为了解决在Python2.2引入的新类所带来的方法解析顺序问题,我采取的方案是在类定义时就计算出它的MRO,并存储为该类对象的一个属性。官方文档中MRO的计算方法为:深度优先,从左到右遍历基类,这个与经典MRO一致,但是如果任何类在搜索中是重复的,只有最后一个出现的位置被保留,其余会从MRO list中删除。因此我们前面这个例子中,搜索顺序将会是D, B, C, A(经典类采用经典MRO,则会是D, B, A, C, A)。

实际上MRO的计算比文档所说的要更复杂。我发现某些情况下新的MRO算法结果不理想。因此还存在一个特例,用于处理两个基类在两个不同的派生类中顺序不同,而这两个派生类又被另外一个类继承的情况。如下代码所示:

class A(object): pass
class B(object): pass
class X(A, B): pass
class Y(B, A): pass
class Z(X, Y): pass

利用文档中描述的新MRO算法,Z关于这些类的MRO为Z, X, Y, B, A, object。(这儿的object是通用基类。)。然而,我不希望结果中B出现在A之前。因此实际的MRO会交换其顺序,产生Z, X, Y, A, B, object。直观上说,算法尝试保持基类在搜索时过程中首次出现的顺序。例如,对于类Z,他们基类X应当被首先搜索到,因为在继承的list中排序最靠前。既然X继承自A和B,MRO算法会尝试保持其顺序。这是在我在Python2.2中实际实现的算法,但是文档只提到了前面的不包括特例处理的算法(我幼稚的认为这点小差别不需明言。)。

然而,就在Python 2.2 引入新类不久,Samuele Pedroni就发现了文档中MRO算法与实际代码中观察到结果不一致的现象。而且,在上述特例之外也可能发生不一致情况。详细讨论的结果认为Python2.2采用的MRO是坏的,Python应当采用C3线性化算法,该算法详情见论文”A Monotonic Superclass Linearization for Dylan”(K. Barrett, et al, presented at OOPSLA’96)。

本质上,Python 2.2 MRO的主要问题在于不能保持单调性(monotonicity)。在一个复杂的多层次继承情况,每个继承关系都决定了一个直接的查找顺序,如果类A继承类B,则MRO明显应当先查找A后查找B。类似,如果类B多重继承类C和类D,则搜索顺序中类B应该在类C之前,且类C应该在类D之前。

在复杂的多层次继承情况,始终能满足这一规则就称为保持了单调性。也就是说,当你决定类A会在类B之前查找到,应当再也不会遇到类B需要在类A之前查找的情况(否则,结果是未定义的,应该拒绝这种情况下的多层次继承)。以前的MRO算法未能做到这一点,新的C3算法则在保证单调性方面发挥了效用。基本上,C3的目的在于让你依据复杂多层次继承中所有的继承关系进行排序,如果所有顺序关系都能满足,则排序结果就满足单调性。否则,无法得到确定的顺序,算法会报错,拒绝运行。

于是,在Python 2.3,摈弃了我手工作坊的 2.2 MRO算法,改为采用经过学术审核检验的C3算法。带来的结果之一就是当多层次继承中存在基类顺序不一致情况时,Python将会拒绝这种类继承。还是参加前面例子,对于类X和类Y就存在顺序上不一致,对于类X,规则判定类A应该在类B之前被检查。然而对于类Y,规则认为类B应该在类A之前被检查。单独情况下,这种不一致是可以接受的,但是如果X和Y共同作为另外一个类(例中定义的类Z)的基类,C3算法就会拒绝这种继承关系。这个也算是对应了Zen of Python的”errors should never pass silently”规则。

英文原文链接: http://python-history.blogspot.com/2010/06/method-resolution-order.html
原文作者: Guido van Rossum

译者: Jabari-Bi

Method Resolution Order – Python类的方法解析顺序的更多相关文章

  1. python---方法解析顺序MRO(Method Resolution Order)<以及解决类中super方法>

    MRO了解: 对于支持继承的编程语言来说,其方法(属性)可能定义在当前类,也可能来自于基类,所以在方法调用时就需要对当前类和基类进行搜索以确定方法所在的位置.而搜索的顺序就是所谓的「方法解析顺序」(M ...

  2. 【转】python---方法解析顺序MRO(Method Resolution Order)<以及解决类中super方法>

    [转]python---方法解析顺序MRO(Method Resolution Order)<以及解决类中super方法> MRO了解: 对于支持继承的编程语言来说,其方法(属性)可能定义 ...

  3. Python的程序结构&lbrack;2&rsqb; -&gt&semi; 类&sol;Class&lbrack;2&rsqb; -&gt&semi; 方法解析顺序 MRO

    方法解析顺序 / MRO (Method Resolution Order) 关于方法解析顺序(MRO)的详细内容可以参考文末链接,这里主要对 MRO 进行简要的总结说明以及一些练习示例. 经典类和新 ...

  4. Method Resolve Order &lpar;MRO&rpar; - 类对象属性的解析顺序

    Method Resolve Order (MRO) - 类对象属性的解析顺序 Python 支持多重继承, 此时就需要解决按照何种顺序来解析属性的问题.类的继承关系在一个特殊的类属性中指定(__mr ...

  5. 转 -- Python&colon; 多继承模式下 MRO&lpar;Method Resolution Order&rpar; 的计算方式关乎super

    大家可能已经知道了,在 Python 3(Python 2 的新式类)中多继承模式是使用 C3 算法来确定 MRO(Method Resolution Order) 的. 那么具体是怎么计算的呢?本文 ...

  6. Python的方法解析顺序&lpar;MRO&rpar;&lbrack;转&rsqb;

    本文转载自: http://hanjianwei.com/2013/07/25/python-mro/ 对于支持继承的编程语言来说,其方法(属性)可能定义在当前类,也可能来自于基类,所以在方法调用时就 ...

  7. sqlalchemy mark-deleted 和 python 多继承下的方法解析顺序 MRO

    sqlalchemy mark-deleted 和 python 多继承下的方法解析顺序 MRO 今天在弄一个 sqlalchemy 的数据库基类的时候,遇到了跟多继承相关的一个小问题,因此顺便看了一 ...

  8. python 方法解析顺序 mro

    一.概要: mor(Method Resolution Order),即方法解析顺序,是python中用于处理二义性问题的算法 二义性: 1.两个基类,A和B都定义了f()方法,c继承A和B那么C调用 ...

  9. Python中的MRO&lpar;方法解析顺序&rpar;&lbrack;转载&rsqb;

    本文转载至: http://hanjianwei.com/2013/07/25/python-mro/ 对于支持继承的编程语言来说,其方法(属性)可能定义在当前类,也可能来自于基类,所以在方法调用时就 ...

随机推荐

  1. 开放-封闭原则&lpar;OCP&rpar;开-闭原则 和 依赖倒转原则,单一职责原则

    单一职责原则 1.单一职责原则(SRP),就一个类而言,应该仅有一个引起它变化的原因 2.如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会消弱或抑制这个类完成其他职责的能力. ...

  2. int main&lpar;int argc&comma; char &ast; argv&lbrack;&rsqb;&rpar; 里的异常处理

    #import <UIKit/UIKit.h> #import "AppDelegate.h" int main(int argc, char * argv[]) { ...

  3. Configure xterm Fonts and Colors for Your Eyeball

    https://wiki.mpich.org/mpich/index.php/Configure_xterm_Fonts_and_Colors_for_Your_Eyeball Screenshot ...

  4. Hashtable与HashMap区别(2)

    提到hashtable,先要澄清两个问题hashCode与equals().Hashtable有容量和加载因子,容量相当于桶,因子相当于桶里的对象.而hashCode我们可以把它理解为桶的序号,所以H ...

  5. 【Android】Android实现截取当前屏幕图片并保存至SDCard

    功能 1. 实现截取当前屏幕的功能. 2. 把截取的图片保存到SDCard中的某个目录文件夹下面. Java代码 package com.app.test01; import java.io.File ...

  6. TS流文件

    简单介绍编辑 随着从HDTV录制的高清节目在网上的流传,烧友们对TS这个名词大概已经不陌生了.但随之而来就是怎样播放.怎样加入字幕等等的一系列问题.本文将重点介绍一下这方面的应用操作. 先来简要介绍一 ...

  7. &lbrack;置顶&rsqb; 让我爱恨的ThinkPHP Relation

    还记得第一次用ThinkPHP的relation,做了一个关联查询,觉得特别好用.有那么一天尝试着用关联插入,怎么插,都插不进,我插,我擦! 后来在龙哥的指点下算是成功的实践了一次,后来怎么用都不顺, ...

  8. &lpar;4&rpar;Microsoft office Word 2013版本操作入门&lowbar;插入图片及图片的排版

    1.word中插入图片和文绕图 1.1插入图片 :点击[插入]-->[图片] 或者 [联机图片]从网上选择. 1.2文字环绕: [格式] --->点击[位置]   .[自动换行]  进行图 ...

  9. java 加密 解密 Illegal key size

    java.security.InvalidKeyException: Illegal key size   今天遇到一个奇怪的问题. 自己做的加签验签功能已经没有问题了,本地测试通过,同事放到服务器上 ...

  10. 常用的Date对象和Math对象方法

    Date对象方法: 当前用户本地时间 let time = new Date(); 获取整数年 console.log(time.getFullYear()); 获取当前月(月份要加1) consol ...