super 是一个内置类,可用于访问属于某个对象的超类的属性。

如果你已经习惯于通过直接调用父类并传入 self 作为第一个参数来访问类的属性或 方法,那么 super 的用法会有些令人困惑。这是非常陈旧的模式,但仍然可以在一些代码 库中找到(特别是遗留项目)。参见以下代码: class Mama: # 旧的写法 def says(self): print('do your homework') class Sister(Mama): def says(self): Mama.says(self) print('and clean your bedroom') 在解释器会话中运行,它会给出如下结果:

Sister().says() do your homework and clean your bedroom 重点看一下 Mama.says(self)这一行,这里我们使用刚刚提到的方法来调用超类 (即 Mama 类)的 says()方法,并将 self 作为参数传入。也就是说,调用的是属于 Mama 的 says()方法。但它的作用对象由 self 参数给出,在这个例子中是一个 Sister 实例。 而 super 的用法如下所示: class Sister(Mama): def says(self): super(Sister, self).says() print('and clean your bedroom') 或者,你也可以使用 super()调用的简化形式如下: class Sister(Mama): def says(self): super().says() print('and clean your bedroom') super 的简化形式(不传入任何参数)可以在方法内部使用,但 super 的使用并不 限于方法。在代码中需要调用给定实例的超类方法的任何地方都可以使用它。不过,如果 super 不在方法内部使用,那么必须给出如下参数: anita = Sister() super(anita. __class __, anita).says() do your homework

最后,关于 super 还有很重要的一点需要注意,就是它的第二个参数是可选的。如果 只提供了第一个参数,那么 super 返回的是一个未绑定(unbound)类型。这一点在与 classmethod 一起使用时特别有用,如下所示: class Pizza: def init(self, toppings): self.toppings = toppings def repr(self): return "Pizza with " + " and ".join(self.toppings) @classmethod def recommend(cls): """推荐任意馅料(toppings)的某种披萨。""" return cls(['spam', 'ham', 'eggs']) class VikingPizza(Pizza): @classmethod def recommend(cls): """推荐与 super 相同的内容,但多加了午餐肉(spam)。""" recommended = super(VikingPizza).recommend() recommended.toppings += ['spam'] * 5 return recommended 注意,零参数的 super()形式也可用于被 classmethod 装饰器装饰的方法。在这样 的方法中无参数调用的 super()被看作是仅定义了第一个参数。 前面提到的使用实例很容易理解,但如果面对多重继承模式,super 将变得难以使用。 在解释这些问题之前,理解何时应避免使用 super 以及方法解析顺序(Method Resolution Order,MRO)在 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 都从其继承, 参见图 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']