Python 高手编程系列三千四百一十四:Python 2 中的旧式类与 super
Python 2 中 super()的工作原理几乎完全相同。调用签名的唯一区别在于简化的零参数形式不可用因此必须始终提供至少一个参数。对于想要编写跨版本兼容的代码的程序员来说另一件重要的事情是Python 2 中的super 只适用于新式类。在早期版本的 Python 中所有类并没有一个共同的祖先 object。Python 所有的 2.x 版本中都保留了旧式类目的是为了向后兼容所以在这些版本中如果类的定义中没有指定祖先那么它就被解释为旧式类且不能使用 super如下所示class OldStyle1:passclass OldStyle2():passPython 2 中的新式类必须显式继承 object 或其他新式类class NewStyleClass(object):passclass NewStyleClassToo(NewStyleClass):passPython 3 不再保留旧式类的概念因此没有继承任何其他类的类都隐式地继承自object。也就是说显式声明某个类继承自 object 似乎是冗余的。通用的良好实践是不包括冗余代码。但在这个例子中只有该项目不再用于任何 Python 2 版本时删除这些冗余才是好的做法。如果代码想要保持 Python 的跨版本兼容那么必须始终将 object 作为所有基类的祖先即使这在 Python 3 中是冗余的。不这么做的话这些类将被解释为旧式类最终会导致难以诊断的问题。理解 Python 的方法解析顺序Python 的方法解析顺序是基于 C3这是为 Dylan 编程语言http://opendylan.org构建的MRO。Michele Simionato 编写的参考文档位于http://www.python.org/download/releases/2.3/mro。它描述了 C3 是如何构建一个类的线性化也叫优先级即祖先的有序列表。这个列表可用于属性查找。本节后面将会对 C3 算法做进一步说明。MRO 的变化是用于解决创建公共基本类型object所引入的问题。在使用 C3 线性化方法之前如果一个类有两个祖先参见图 3-1那么对于不使用多重继承模型的简单情况来说方法解析顺序的计算和跟踪都非常简单。下面是 Python 2 中的一个代码示例没有使用 C3 作为方法解析顺序class Base1:passclass Base2:def method(self):print(‘Base2’)class MyClass(Base1, Base2):pass在交互式会话中运行下列代码可以看到这种方法解析的作用如下MyClass().method()Base2当调用 MyClass().method()时解释器会首先在 MyClass 中查找这一方法然后在 Base1 中查找最终在 Base2 中找到如果我们在两个基类之上引入某个 CommonBase 类Base1 和 Base2 都从其继承问题将变得更加复杂。其结果为根据“从左到右、深度优先”规则的简单解析顺序在查找 Base2 类之前就通过 Base1 类回到顶部。这一算法会导致反直觉的结果。在某些情况下执行的方法可能并不是在继承树中最为接近的那个方法。在 Python 2 中如果使用的是旧式类不继承自 object仍然存在这样的算法。下面是 Python 2 中旧式类的旧式方法解析的示例class CommonBase:def method(self):print(‘CommonBase’)class Base1(CommonBase):passclass 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):passclass Base2(CommonBase):def method(self):print(‘Base2’)class MyClass(Base1, Base2):pass这种用法表明C3 序列化会挑选最接近的祖先的方法MyClass().method()Base2Python 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.namefor k in klass.mro]L(MyClass)[‘MyClass’, ‘Base1’, ‘Base2’, ‘CommonBase’, ‘object’]