有什么区别:

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

and:

class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

我看到super在只有单一继承的类中被大量使用。我可以理解为什么在多重继承中使用它,但不清楚在这种情况下使用它的优势是什么。


当前回答

简单来说就是Super()

每个Python实例都有一个创建它的类。 Python中的每个类都有一个祖先类链。 使用super()委托的方法工作到实例类的链中的下一个祖先。

例子

这个小例子涵盖了所有有趣的情况:

class A:
    def m(self):
        print('A')

class B(A):
    def m(self):
        print('B start')
        super().m()
        print('B end')
        
class C(A):
    def m(self):
        print('C start')
        super().m()
        print('C end')

class D(B, C):
    def m(self):
        print('D start')
        super().m()
        print('D end')

调用的确切顺序由调用该方法的实例决定:

>>> a = A()
>>> b = B()
>>> c = C()
>>> d = D()

例如a,没有super调用:

>>> a.m()
A

例如实例b,祖先链为b -> A ->对象:

>>> type(b).__mro__   
(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

>>> b.m()
B start
A
B end

例如c,父链为c -> A ->对象:

>>> type(c).__mro__   
(<class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

>>> c.m()
C start
A
C end

例如,祖先链d更有趣d -> B -> C -> A ->对象(mro代表方法解析顺序):

>>> type(d).__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

>>> d.m()
D start
B start
C start
A
C end
B end
D end

更多的信息

回答了“super在Python中做什么?”的问题后,下一个问题是如何有效地使用它。请看这个循序渐进的教程或者这个45分钟的视频。

其他回答

有什么不同?

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会给代码的用户带来不必要的限制。

我曾经使用过super(),并且认识到我们可以改变调用顺序。

例如,我们有下一个层次结构:

    A
   / \
  B   C
   \ /
    D

在这种情况下,D的MRO将是(仅适用于Python 3):

In [26]: D.__mro__
Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

让我们创建一个类,其中super()在方法执行后调用。

In [23]: class A(object): #  or with Python 3 can define class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          print("I'm from B")
...:          super().__init__()
...:   
...: class C(A):
...:      def __init__(self):
...:          print("I'm from C")
...:          super().__init__()
...:  
...: class D(B, C):
...:      def __init__(self):
...:          print("I'm from D")
...:          super().__init__()
...: d = D()
...:
I'm from D
I'm from B
I'm from C
I'm from A

    A
   / ⇖
  B ⇒ C
   ⇖ /
    D

所以我们可以看到分辨率顺序和MRO是一样的。但是当我们在方法的开头调用super()时:

In [21]: class A(object):  # or class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          super().__init__()  # or super(B, self).__init_()
...:          print("I'm from B")
...:   
...: class C(A):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from C")
...:  
...: class D(B, C):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from D")
...: d = D()
...: 
I'm from A
I'm from C
I'm from B
I'm from D

我们有一个不同的顺序,它颠倒了MRO元组的顺序。

    A
   / ⇘
  B ⇐ C
   ⇘ /
    D 

对于额外的阅读,我推荐下面的答案:

带有super的C3线性化示例(一个大层次结构) 新旧样式类之间的重要行为变化 关于新式课程的内幕

super()在单继承中的好处是微乎其微的——大多数情况下,您不必将基类的名称硬编码到每个使用其父方法的方法中。

然而,如果没有super(),几乎不可能使用多重继承。这包括常见的习惯用法,如mixin、接口、抽象类等。这将扩展到稍后扩展您的代码。如果有人后来想要编写一个扩展Child和mixin的类,他们的代码将无法正常工作。

考虑下面的代码:

class X():
    def __init__(self):
        print("X")

class Y(X):
    def __init__(self):
        # X.__init__(self)
        super(Y, self).__init__()
        print("Y")

class P(X):
    def __init__(self):
        super(P, self).__init__()
        print("P")

class Q(Y, P):
    def __init__(self):
        super(Q, self).__init__()
        print("Q")

Q()

如果将Y的构造函数更改为X.__init__,你将得到:

X
Y
Q

但是使用super(Y, self).__init__(),你会得到:

X
P
Y
Q

P或Q甚至可能涉及到另一个文件,当你写X和Y时,你不知道,所以基本上,当你写类Y(X)时,你不知道super(Child, self)会引用什么,甚至Y的签名也像Y(X)一样简单。这就是为什么超级可能是更好的选择。

所有这些不都假定基类是一个新型的类吗?

class A:
    def __init__(self):
        print("A.__init__()")

class B(A):
    def __init__(self):
        print("B.__init__()")
        super(B, self).__init__()

将不能在python2中工作。类A必须是new-style,即:类A(对象)