7.7 关于super的真相

从Python的最早期开始,开发人员就能够通过单继承和多继承扩展他们的类。不过,很多开发人员似乎并不理解这些机制是如何工作的,以及与其关联的super()方法。

单继承和多继承各有利弊,组成或者鸭子类型都超出了本书的讨论范围,如果你不了解这些概念,那么建议读一些相关的资料以便形成自己的观点。

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

多继承仍然被广泛使用,尤其在使用了混合模式的代码里。这也是了解它仍然很重要的原因,因为它是Python核心的一部分。

 注意

混入(mixin)类是指继承自两个或两个以上的类,并将它们的特性组合在一起。

到目前为止,你应该知道,在Python中类也是对象,而且对于这种构建类的特定声明方式应该非常熟悉了:class classname(expression of inheritance)

括号内的部分是一个Python表达式,返回一个当前类要继承的类对象列表。通常,都会直接指定,但也可以像下面这样写:

>>> def parent():
...   return object
...
>>> class A(parent()):
...   pass
...
>>> A.mro()
[<class '__main__.A'>, <type 'object'>]

不出所料,可以正常运行:类A继承自父类object。类方法mro()返回方法解析顺序(method resolution order)用于解析属性。当前的MRO系统是在Python 2.3中第一次被实现的,关于其内部工作机制详见Python 2.3 release notes(http://www.python.org/download/releases/2.3/mro)。

你已经知道了调用父类方法的正规方式是通过super()函数,但你很可能不知道super()函数实际上是一个构造器,每次调用它都会实例化一个super对象。它接收一个或两个参数,第一个参数是一个类,第二个参数是一个子类或第一个参数的一个实例。

构造器返回的对象就像是第一个参数的父类的一个代理。它有自己的__getattribute__方法去遍历MRO列表中的类并返回第一个满足条件的属性:

>>> class A(object):
...   bar = 42
...   def foo(self):
...       pass
...
>>> class B(object):
...   bar = 0
...
>>> class C(A, B):
...   xyz = 'abc'
...
>>> C.mro()
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <type 'object'>]
>>> super(C, C()).bar
42
>>> super(C, C()).foo
<bound method C.foo of <__main__.C object at 0x7f0299255a90>>
>>> super(B).__self__
>>> super(B, B()).__self__
<__main__.B object at

当请求C的实例的访问其super对象的一个属性时,它会遍历MRO列表,并从第一个包含这个属性的类中返回这个属性。

前一个例子中使用了绑定的super对象,也就是说,通过两个参数调用super。如果只通过一个参数调用super(),则会返回一个未绑定的super对象:

>>> super(C)
<super: <class 'C'>, NULL>

由于对象是未绑定的,所以不能通过它访问类属性:

>>> super(C).foo
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
AttributeError: 'super' object has no attribute 'foo'
>>> super(C).bar
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
AttributeError: 'super' object has no attribute 'bar'
>>> super(C).xyz
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
AttributeError: 'super' object has no attribute 'xyz'

粗一看,似乎这种super对象没什么用,但是super类通过某种方式实现了描述符协议(也就是__get__),这种方式能够让未绑定的super对象像类属性一样有用:

>>> class D(C):
...   sup = super(C)
...
>>> D().sup
<super: <class 'C'>, <D object>>
>>> D().sup.foo
<bound method D.foo of <__main__.D object at 0x7f0299255bd0>>
>>> D().sup.bar
42

通过用实例和属性名字作为参数来调用未绑定的super对象的__get__方法(super(C).__get__(D(), 'foo'))能够让它找到并解析foo

 注意

尽管你可能没有听说过描述符协议,但是你很可能在不知道的情况已经通过@property装饰器使用过它。它是Python的一种机制,允许对象以属性的方式进行存储以返回其他东西而非其自身。本书不会讨论这个协议的具体细节,想详细了解可参考Python数据模型文档(http://docs.python.org/2/reference/datamodel.html#implementing-descriptors)。

在许多场景中使用super都是很有技巧的,例如处理继承链中不同的方法签名。遗憾的是,除了类似让方法接收*args, **kwargs参数这样的技巧,针对这个问题同样“没有银弹”。

在Python 3中,super()变得更加神奇:可以在一个方法中不传入任何参数调用它。但没有参数传给super()时,它会为它们自动搜索栈框架:

class B(A):
   def foo(self):
     super().foo()

super是在子类中访问父类属性的标准方式,应该尽量使用它。它能确保父类方法的协作调用不出意外,例如在多继承时父类方法没有被调用或者被调用了两次。