- 在子类中调用父类的某个已经被覆盖的方法,需要使用 super() 函数。
class Parent(object):
def spam(self):
print('Parent.spam')
class Child(Parent):
def spam(self):
print('Child.spam')
super().spam() # Call parent spam()
if __name__ == "__main__":
c = Child()
c.spam()
- super() 函数的一个常见用法是在 init() 方法中确保父类被正确的初始化了。
class Parent(object):
def __init__(self):
self.x = 0
class Child(Parent):
def __init__(self):
super().__init__()
self.y = 1
if __name__ == "__main__":
c = Child()
print(c.y, c.x)
- 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)
if __name__ == "__main__":
p = Proxy(object)
- _setattr_() 的实现包含一个名字检查。 如果某个属性名以下划线(_)开头,就通过 super() 调用原始的_setattr_() , 否则的话就委派给内部的代理对象 self._obj 去处理。 这看上去有点意思,因为就算没有显式的指明某个类的父类, super() 仍然可以有效的工作。
- 在多继承中应该使用super,而不是直接用类.方法直接父类调用
Python继承的原理是根据一个所谓的方法解析顺序(MRO)列表。 这个MRO列表就是一个简单的所有基类的线性顺序表。
#例如
>>> B.__mro__
(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
- 为了实现继承,Python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止,而这个MRO列表的构造是通过一个C3线性化算法来实现的,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
- 子类会先于父类被检查。
- 多个父类会根据它们在列表中的顺序被检查。
- 如果对下一个类存在两个合法的选择,选择第一个父类。
当你使用 super() 函数时,Python会在MRO列表上继续搜索下一个类。 只要每个重定义的方法统一使用 super() 并只调用它一次, 那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次。
然而,由于 super() 可能会调用不是你想要的方法,你应该遵循一些通用原则。 首先,确保在继承体系中所有相同名字的方法拥有可兼容的参数签名(比如相同的参数个数和参数名称)。 这样可以确保 super() 调用一个非直接父类方法时不会出错。 其次,最好确保最顶层的类提供了这个方法的实现,这样的话在MRO上面的查找链肯定可以找到某个确定的方法。