8.7 调用父类方法
问题
你想在子类中调用父类的某个已经被覆盖的方法。
解决方案
为了调用父类(超类)的一个方法,可以使用 super()
函数。
super()
函数的一个常见用法是在 __init__()
方法中确保父类被正确的初始化了:
class A:
def __init__(self):
self.x = 0
class B(A):
def __init__(self):
super().__init__()
self.y = 1
super()
的另外一个常见用法出现在覆盖Python特殊方法的代码中,比如:
class Proxy:
def __init__(self, obj):
self._obj = obj
# Delegate attribute lookup to internal obj
def __getattr__(self, name):
return getattr(self._obj, name)
# Delegate attribute assignment
def __setattr__(self, name, value):
if name.startswith('_'):
super().__setattr__(name, value) # Call original __setattr__
else:
setattr(self._obj, name, value)
讨论
对于你定义的每一个类,Python会计算出一个所谓的方法解析顺序(MRO)列表。 这个MRO列表就是一个简单的所有基类的线性顺序表。例如:
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,
<class '__main__.Base'>, <class 'object'>)
为了实现继承,Python 会在 MRO 列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个 MRO 列表的构造是通过一个 C3 线性化算法来实现的。 我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
子类会先于父类被检查
多个父类会根据它们在列表中的顺序被检查
如果对下一个类存在两个合法的选择,选择第一个父类
老实说,你所要知道的就是 MRO 列表中的类顺序会让你定义的任意类层级关系变得有意义。
当你使用 super()
函数时,Python会在MRO列表上继续搜索下一个类。 只要每个重定义的方法统一使用 super()
并只调用它一次, 那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次。
super()
有个令人吃惊的地方是它并不一定去查找某个类在MRO中下一个直接父类, 你甚至可以在一个没有直接父类的类中使用它。例如,考虑如下这个类:
class A:
def spam(self):
print('A.spam')
super().spam()
如果你试着直接使用这个类就会出错,但是,如果你使用多继承的话看看会发生什么:
>>> class B:
... def spam(self):
... print('B.spam')
...
>>> class C(A,B):
... pass
...
>>> c = C()
>>> c.spam()
A.spam
B.spam
在定义混入类的时候这样使用 super()
是很普遍的。
然而,由于 super()
可能会调用不是你想要的方法,你应该遵循一些通用原则。 首先,确保在继承体系中所有相同名字的方法拥有可兼容的参数签名(比如相同的参数个数和参数名称)。 这样可以确保 super()
调用一个非直接父类方法时不会出错。 其次,最好确保最顶层的类提供了这个方法的实现,这样的话在MRO上面的查找链肯定可以找到某个确定的方法。
Last updated
Was this helpful?