0
点赞
收藏
分享

微信扫一扫

理解 Python 的方法解析顺序

Python 的方法解析顺序是基于 C3,这是为 Dylan 编程语言(http://opendylan.org)构建的

MROMichele Simionato 编写的参考文档位于 http://www.python.org/download/releases/2.3/mro

它描述了 C3 是如何构建一个类的线性化(也叫优先级,即祖先的有序列表)。这个列表可

用于属性查找。本节后面将会对 C3 算法做进一步说明。

MRO 的变化是用于解决创建公共基本类型(object)所引入的问题。在使用 C3 线

性化方法之前,如果一个类有两个祖先(参见图 3-1),那么对于不使用多重继承模型的简

单情况来说,方法解析顺序的计算和跟踪都非常简单。下面是 Python 2 中的一个代码示例,

没有使用 C3 作为方法解析顺序:

class Base1:

pass

class Base2:

def method(self):

print('Base2')

class MyClass(Base1, Base2):

pass

在交互式会话中运行下列代码,可以看到这种方法解析的作用如下:

>>> MyClass().method()

Base2

当调用 MyClass().method()时,解释器会首先在 MyClass 中查找这一方法,然

后在 Base1 中查找,最终在 Base2 中找到:

如果我们在两个基类之上引入某个 CommonBase 类(Base1 和 Base2 都从其继承,

参见图 3-2),问题将变得更加复杂。其结果为,根据“从左到右、深度优先”规则的简单

解析顺序,在查找 Base2 类之前就通过 Base1 类回到顶部。这一算法会导致反直觉的结

果。在某些情况下,执行的方法可能并不是在继承树中最为接近的那个方法。


Python 2 中,如果使用的是旧式类(不继承自 object),仍然存在这样的算法。下

面是 Python 2 中旧式类的旧式方法解析的示例:

class CommonBase:

def method(self):

print('CommonBase')

class Base1(CommonBase):

pass

class Base2(CommonBase):

def method(self):

print('Base2')

class MyClass(Base1, Base2):

pass

在交互式会话中运行以下代码,可以看到,Base2.method()没有被调用,虽然在类

层次结构中 Base2 比 CommonBase 要更近一些:

>>> MyClass().method()

CommonBase

这样的继承情景是极其少见的,因此这更多的是一个理论问题而不是实践问题。标准库不

用这种方式构造继承的层次结构,许多开发人员也都认为这是不好的实践。但由于在类型层次

结构顶部引入了 object,在语言的 C 边(C side)出现了多重继承问题,进而导致了子类化

时的冲突。还要注意,现在 Python 3 中所有类都具有相同的共同祖先。由于使用现有 MRO 使

其正常工作要花费太多的精力,所以提供一个新的 MRO 是更为简单、快捷的解决方案。

因此,在 Python 3 中运行同样的示例,会给出以下不同的结果:

class CommonBase:

def method(self):

print('CommonBase')

class Base1(CommonBase):

pass

class Base2(CommonBase):

def method(self):

print('Base2')

class MyClass(Base1, Base2):

pass

这种用法表明,C3 序列化会挑选最接近的祖先的方法:

>>> MyClass().method()

Base2

Python MRO 是基于对基类的递归调用。为了总结本节开头引用的 Michele Simionato

的文章,将 C3 符号应用到我们的示例中,如下所示:

L[MyClass(Base1, Base2)] =

MyClass + merge(L[Base1], L[Base2], Base1, Base2)

这里 L[MyClass]是 MyClass 类的线性化,而 merge 是合并多个线性化结果的具体

算法。

因此,综合的描述应该是(正如 Simionato 所言):

“C 的线性化是 C 加上父类的线性化和父类列表的合并的总和。”

merge 算法负责删除重复项并保持正确的顺序。在文章中对该算法的描述为(根据我

们的例子做了适当修改):

“取第一个列表的表头(head),即 L[Base1][0]。如果这个表头不在其他

任何列表的表尾(tail),那么就将它添加到 Myclass 的线性化中,并从合并的

列表里删除;否则的话,查看下一个列表的表头,如果是一个好的表头就将其

取出。

然后重复这一操作,直到所有的类都被删除或者找不到好的表头为止。在

后一种情况下,无法构建合并,Python 2.3 将拒绝创建 MyClass 类,并引发一

个异常。”

head(表头)是列表的第一个元素,而 tail(表尾)则包含其余元素。例如,在(Base1,

Base2, ..., BaseN)中,Base1 是 head,而(Base2, ..., BaseN)则是 tail。

换句话说,C3 对每个父类进行递归深度查找以得到一个列表序列。然后,如果某个类

包含在多个列表中,它会利用层次结构消歧(hierarchy disambiguation)计算出从左到右的

规则,以此合并所有列表。

其结果如下:

def L(klass):

return [k.__name__ for k in klass.__mro__]

>>> L(MyClass)

['MyClass', 'Base1', 'Base2', 'CommonBase', 'object']

举报

相关推荐

0 条评论