Python 高手编程系列一百二十四:Python 2 中的旧式类与 super

Python 2 中 super()的工作原理几乎完全相同。调用签名的唯一区别在于简化的零参
数形式不可用,因此必须始终提供至少一个参数。
对于想要编写跨版本兼容的代码的程序员来说,另一件重要的事情是,Python 2 中的
super 只适用于新式类。在早期版本的 Python 中,所有类并没有一个共同的祖先 object。
Python 所有的 2.x 版本中都保留了旧式类,目的是为了向后兼容,所以在这些版本中,如
果类的定义中没有指定祖先,那么它就被解释为旧式类,且不能使用 super,如下所示:
class OldStyle1:
pass
class OldStyle2():
pass
Python 2 中的新式类必须显式继承 object 或其他新式类:
class NewStyleClass(object):
pass
class NewStyleClassToo(NewStyleClass):
pass
Python 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:
pass
class 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):
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’]

  • 16
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值