预计阅读本页时间:-
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是在子类中访问父类属性的标准方式,应该尽量使用它。它能确保父类方法的协作调用不出意外,例如在多继承时父类方法没有被调用或者被调用了两次。