有什么不同?
SomeBaseClass.__init__(self)
意思是调用SomeBaseClass的__init__。而
super().__init__()
意味着在实例的方法解析顺序(MRO)中,从SomeBaseClass的子类(定义此方法的子类)后面的父类调用绑定__init__。
如果实例是这个子类的子类,那么在MRO中接下来可能会有一个不同的父类。
解释简单
当您编写一个类时,您希望其他类能够使用它。Super()使其他类更容易使用您正在编写的类。
正如Bob Martin所说,一个好的架构允许您尽可能地推迟决策。
Super()可以启用这种体系结构。
当另一个类继承了您所编写的类时,它也可以从其他类继承。根据方法解析类的顺序,这些类可以在这个__init__之后有一个__init__。
如果没有super,您可能会硬编码正在编写的类的父类(就像示例所做的那样)。这意味着你不能在MRO中调用下一个__init__,因此你不能重用其中的代码。
如果您编写自己的代码供个人使用,您可能不会关心这个区别。但是如果您希望其他人使用您的代码,使用super可以为代码用户提供更大的灵活性。
Python 2与3
这适用于Python 2和3:
super(Child, self).__init__()
这只适用于Python 3:
super().__init__()
它的工作方式是在堆栈框架中向上移动并获得方法的第一个参数(对于实例方法通常是self,对于类方法是cls -但也可以是其他名称),并在自由变量中查找类(例如Child)(在方法中使用名称__class__作为自由闭包变量)。
我过去更喜欢演示使用super的交叉兼容方式,但现在Python 2基本上已弃用,我将演示Python 3的做事方式,即不带参数地调用super。
间接向前兼容
它给了你什么?对于单一继承,从静态分析的角度来看,问题中的示例实际上是相同的。但是,使用super为您提供了一层具有向前兼容性的间接层。
向前兼容性对于经验丰富的开发人员来说非常重要。你希望你的代码在你修改它的时候保持最小的变化。当你查看你的复习历史时,你想确切地知道什么时候发生了变化。
你可以从单继承开始,但是如果你决定添加另一个基类,你只需要改变一行基类——如果你继承的类中的基改变了(比如添加了一个mixin),你在这个类中什么都不会改变。
在python2中,将参数设置为super和正确的方法参数可能会让人有点困惑,所以我建议使用python3唯一的调用方法。
如果您知道您正确地使用了super单继承,那么接下来的调试就不那么困难了。
依赖注入
其他人可以使用你的代码并在方法决议中注入父元素:
class SomeBaseClass(object):
def __init__(self):
print('SomeBaseClass.__init__(self) called')
class UnsuperChild(SomeBaseClass):
def __init__(self):
print('UnsuperChild.__init__(self) called')
SomeBaseClass.__init__(self)
class SuperChild(SomeBaseClass):
def __init__(self):
print('SuperChild.__init__(self) called')
super().__init__()
假设你向你的对象中添加了另一个类,并想在Foo和Bar之间注入一个类(用于测试或其他原因):
class InjectMe(SomeBaseClass):
def __init__(self):
print('InjectMe.__init__(self) called')
super().__init__()
class UnsuperInjector(UnsuperChild, InjectMe): pass
class SuperInjector(SuperChild, InjectMe): pass
使用非超子类无法注入依赖项,因为你使用的子类已经硬编码了方法,在它自己的方法之后被调用:
>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called
但是,使用super的子类可以正确地注入依赖项:
>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called
处理注释
这究竟为什么有用呢?
Python通过C3线性化算法线性化一个复杂的继承树,以创建一个方法解析顺序(MRO)。
我们希望按此顺序查找方法。
对于在父类中定义的方法,如果要按此顺序查找下一个没有super的方法,则必须这样做
从实例的类型获取mro
寻找定义方法的类型
使用该方法查找下一个类型
绑定该方法并使用预期的参数调用它
UnsuperChild不应该访问InjectMe。为什么结论不是“总是避免使用super”?我错过了什么?
UnsuperChild不能访问InjectMe。UnsuperInjector可以访问InjectMe,但不能从它继承自UnsuperChild的方法中调用该类的方法。
两个子类都打算调用MRO中接下来出现的同名方法,该方法可能是创建时它不知道的另一个类。
没有对其父方法进行超硬编码的方法——因此限制了其方法的行为,并且子类不能在调用链中注入功能。
super的灵活性更大。方法的调用链可以被拦截并注入功能。
您可能不需要该功能,但代码的子类可能需要。
结论
总是使用super来引用父类,而不是硬编码。
您想要引用的是顺位的父类,而不是您看到的子类继承的父类。
不使用super会给代码的用户带来不必要的限制。