在Python中,'super'是什么意思?super().__init__()和显式超类__init__()之间的区别是什么?

744

以下两者有何不同:

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

并且:

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

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


这个问题涉及到技术实现细节和访问基类__init__方法的不同方式之间的区别。为了关闭那些OP只是缺少一个super调用并询问为什么基类属性不可用的重复问题,请使用Python类继承:AttributeError:'[SubClass]'对象没有属性'xxx'

11个回答

465

有何不同之处?

SomeBaseClass.__init__(self) 

调用SomeBaseClass__init__函数。

super().__init__()

调用父类的 __init__ 方法意味着在实例的方法解析顺序(MRO)中跟随 SomeBaseClass 的子类(定义此方法的那个子类)。

如果实例是 这个 子类的子类,则在 MRO 中可能会出现其他父类接下来的情况。

super() 使其他类更容易使用您编写的类。

正如 Bob Martin 所说,良好的架构可以尽可能地推迟决策。

super() 可以实现这种架构。

当另一个类继承您编写的类时,它也可以从其他类继承。并且这些类的 __init__ 基于方法解析的类的排序而在此 __init__ 之后。

没有 super,您可能会硬编码您正在编写的类的父类(就像示例一样)。这意味着您将不调用 MRO 中的下一个 __init__,因此无法重用其中的代码。

如果您为个人使用编写自己的代码,则可能不关心此区别。但是,如果您希望其他人使用您的代码,则使用 super 是允许代码用户更大的灵活性之一。

此方法适用于 Python 2 和 3。

super(Child, self).__init__()

这仅适用于 Python 3:

super().__init__()

在没有传入参数的情况下,它会向上移动堆栈帧并获取方法的第一个参数(通常是实例方法的self或类方法的cls,但也可能是其他名称),然后在自由变量中查找类(例如Child)(在方法中,它通过名称__class__作为自由闭包变量进行查找)。

我曾经更喜欢演示使用super的跨兼容方式,但现在Python 2已经被淘汰了,我将演示Python 3的做法,即不带参数调用super

利用超类实现向前兼容的间接性

这给你什么好处?对于单继承,从静态分析的角度来看,问题中的例子几乎相同。然而,使用super可以为你提供一层具有向前兼容性的间接性。

对有经验的开发人员来说,向前兼容非常重要。你希望你的代码在你改变它的时候能够最小限度地工作。当你查看修订历史记录时,你希望能够清楚地看到每个修改的内容。

你可以从单一继承开始,但如果你决定添加另一个基类,那么你只需要更改具有基数的那行代码 - 如果你继承自的类中的基础发生变化(例如添加了一个mixin),那么你在这个类中就不需要做任何更改。

在Python 2中,正确获取super的参数和方法参数可能会有些困惑,因此建议使用Python 3的方法来调用它。

如果你知道你正在正确地使用单一继承中的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,则它必须执行以下步骤才能按照该顺序查找下一个方法:

  1. 获取实例类型的MRO
  2. 查找定义该方法的类型
  3. 查找下一个拥有该方法的类型
  4. 绑定该方法并使用预期的参数进行调用

UnsuperChild 不应该可以访问 InjectMe。为什么结论不是“总是避免使用 super”?我错过了什么吗?

UnsuperChild 不能访问 InjectMe。是 UnsuperInjector 可以访问 InjectMe ,但它无法从继承自 UnsuperChild 的方法中调用该类的方法。

两个子类都打算调用名称相同的方法,该方法在 MRO 中后继,这可能是它创建时没有意识到的另一个类。

没有使用 super 的那个硬编码了其父类的方法,因此它限制了其方法的行为,并且子类无法在调用链中注入功能。

使用 super 的那个具有更大的灵活性。方法的调用链可以被拦截并注入功能。

您可能不需要该功能,但代码的子类可能需要。

结论

始终使用 super 引用父类,而不是硬编码。

你的意图是引用下一个类而不是特定于子类继承的类。

不使用 super 可能会对您代码的用户造成不必要的约束。


在C语言中,DI的实现方式类似于这个。代码在这里。如果我添加了一个list接口的另一个实现,比如doublylinkedlist,那么应用程序可以平稳地使用它。我可以通过引入config.txt并在加载时链接实现来使我的示例更具可配置性。这是正确的示例吗?如果是,我该如何将您的代码与之相关联?请参见维基百科中DI的第一个优点。在您的代码中,任何新的实现都是可配置的。 - overexchange
通过继承创建新的实现,例如,“Injector”类之一从“InjectMe”类继承。注释不用于讨论,因此建议您在聊天中与他人讨论此问题或在主站点上提出新问题。 - Russia Must Remove Putin
非常好的答案!但是在使用多重继承时,super()和__init__函数会出现一些复杂情况,特别是当类层次结构中的类之间的__init__签名不同时。我已经添加了一个关注这个方面的答案。 - Aviad Rozenhek
2
谢谢您提供这个详细的回答!我在其他地方找不到关于Python 3语法中第二个参数如何推导的信息(即“向上移动堆栈帧并获取方法的第一个参数”)。似乎他们采用了这种隐式语法有些奇怪:虽然打字更少,但与类代码中其他地方的操作方式不太一致,在那里您需要始终明确指定self(例如没有隐式对象变量解析)。 - Roman Shapovalov
@RomanShapovalov 我记得我曾经研究过这个问题,但我不太记得我在哪里找到它了——我想是在源代码中的超级对象定义(用C语言编写)中。如果你想验证机制是否仍然相同,我建议从那里开始。无论机制如何工作,它仍然是一个实现细节,你不应该过于关注它…… - Russia Must Remove Putin

355

super()在单继承中的作用很小,主要是避免在每个使用父类方法的方法中硬编码基类的名称。

然而,在多继承中几乎不可能没有super()。这包括常见的语言习惯,如混合(mixins)、接口(interfaces)、抽象类等等。这也适用于稍后扩展您代码的人。如果有人想编写一个扩展了Child和一个mixin的类,他们的代码将无法正常工作。


34
你能举个例子来说明“它不能正常工作”的意思吗? - Charlie Parker

61

我之前稍微使用过 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 

更多阅读推荐:

  1. C3线性化的超类示例(大型继承层次结构)
  2. 旧式类和新式类之间的重要行为变化
  3. 新式类内幕

我不明白为什么顺序会改变。第一部分我理解为D-B-C-A,因为D是第一个类,然后当加载self(B,C)时最终会打印B、C,然后才是A,因为B(A)、C(A)指向self作为最后一部分。如果我按照这个理解,那么第二部分不应该是B-C-A-D吗?你能给我解释一下吗? - JJson
2
抱歉,我没有注意到每个类实例都是首先用super()初始化的。那么如果是这样的话,应该是A-B-C-D吧?我有点理解A-C-B-D是怎么来的,但仍然无法说服自己,还有点困惑。我的理解是,d = D() 调用了带有2个self参数的类D(B,C),由于super()首先被初始化,因此B与其属性一起被调用,然后在调用D之前不会打印C,因为类D(B,C)包含2个self参数,所以必须执行第二个参数,即类C(A),执行完毕后就没有更多的self参数需要执行了。 - JJson
2
它会先打印C,然后打印B,最后打印D。我是对的吗? - JJson
5
只要你理解了第一个,就很容易理解第二个。就像一个堆栈一样,你将“print”推入堆栈并执行super()。当执行完A后,它开始按照那个堆栈中的顺序打印东西,因此顺序是相反的。 - grant sun
3
它就像递归一样。在第二个例子中,它首先调用所有类并将它们放入队列(或堆栈)中,因为首先调用super()。然后当它到达基类时,它执行基类的print方法并继续到队列中的下一个类(或者像@grantsun所说的那样,在堆栈中)。在第一个例子中,首先调用D的print(),这就是为什么它首先打印"I'm from D",然后才去下一个类,在那里它再次看到print(),然后再调用super()。 - Eli Halych

36

所有这些都假设基类是新式类,对吗?

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

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

不适用于Python 2。 class A 必须是新式类,即: class A(object)


27

当调用super()以解析父类的classmethods、instance methods或staticmethod时,我们需要将当前所在范围的类作为第一个参数传递,以指示我们尝试解析哪个父类的范围,并且作为第二个参数传递感兴趣的对象,以指示我们正在尝试应用该范围到哪个对象上。

考虑一个类层次结构ABC,其中每个类都是其后面的类的父类,abc分别是它们的实例。

super(B, b) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to b, as if b was an instance of A

super(C, c) 
# resolves to the scope of C's parent i.e. B
# and applies that scope to c

super(B, c) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to c

如何在staticmethod中使用super()

例如,在__new__()方法中使用super()

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return super(A, cls).__new__(cls, *a, **kw)

解释:

1- 即使通常情况下__new__()的第一个参数应该是调用类的引用,但在Python中它并没有作为类方法来实现,而是作为静态方法来实现。也就是说,在直接调用__new__()时,必须明确地传递一个类的引用作为第一个参数:

# if you defined this
class A(object):
    def __new__(cls):
        pass

# calling this would raise a TypeError due to the missing argument
A.__new__()

# whereas this would be fine
A.__new__(A)

2- 当调用 super() 来获取父类时,我们将子类 A 作为其第一个参数传递,然后我们传递一个感兴趣的对象的引用,即在调用 A.__new__(cls) 时传递的类引用。在大多数情况下,这也恰好是对子类的引用。但在某些情况下可能不是,例如在多代继承的情况下。

super(A, cls)

3- 由于通常情况下__new__()是一个静态方法, super(A, cls).__new__也会返回一个静态方法,因此需要显式地提供所有参数,包括感兴趣的对象的引用,即cls

super(A, cls).__new__(cls, *a, **kw)

4- 在没有使用super的情况下执行相同的操作

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return object.__new__(cls, *a, **kw)

使用super调用实例方法

例如,在__init__()中使用super()

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        super(A, self).__init__(*a, **kw)

解释:

1- __init__ 是一个实例方法,意味着它的第一个参数是一个对实例的引用。当直接从实例中调用时,引用会被隐式地传递,也就是说你不需要指定它:

# you try calling `__init__()` from the class without specifying an instance
# and a TypeError is raised due to the expected but missing reference
A.__init__() # TypeError ...

# you create an instance
a = A()

# you call `__init__()` from that instance and it works
a.__init__()

# you can also call `__init__()` with the class and explicitly pass the instance 
A.__init__(a)

2- 当在__init__()函数中调用super()时,我们将子类作为第一个参数传递,将感兴趣的对象作为第二个参数传递,通常是对子类实例的引用。

super(A, self)

3- 调用 super(A, self) 返回一个代理,它将解析范围并将其应用于self,就好像它现在是父类的实例。让我们称这个代理为 s。由于 __init__() 是实例方法,调用 s.__init__(...) 将隐式地将 self 的引用作为第一个参数传递给父类的__init__()

4- 如果没有使用super,我们需要显式地向父类版本的__init__()传递一个实例的引用来完成同样的操作。

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        object.__init__(self, *a, **kw)

使用 super 处理 classmethod

class A(object):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        print "A.alternate_constructor called"
        return cls(*a, **kw)

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return super(B, cls).alternate_constructor(*a, **kw)

解释:

1- 类方法可以直接从类调用,并将其第一个参数作为对类的引用。

# calling directly from the class is fine,
# a reference to the class is passed implicitly
a = A.alternate_constructor()
b = B.alternate_constructor()

2- 当在一个classmethod中调用super()以解析其父类的版本时,我们需要将当前子类作为第一个参数传递,以指示我们正在尝试解析哪个父类的范围,并将感兴趣的对象作为第二个参数传递,以指示我们要将该范围应用于哪个对象, 一般而言是指向子类本身或者其子类之一的一个引用。

super(B, cls_or_subcls)

3- 调用 super(B, cls) 将解析到 A 的作用域并将其应用于 cls。由于 alternate_constructor() 是一个类方法,调用 super(B, cls).alternate_constructor(...) 会隐式地将 cls 的引用作为第一个参数传递给 A 版本的 alternate_constructor()

super(B, cls).alternate_constructor()

4- 如果不使用super(),你需要获取对A.alternate_constructor()(即函数的显式版本)的未绑定引用。仅仅这样做是不行的:

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return A.alternate_constructor(cls, *a, **kw)

上面的代码无法运行是因为 A.alternate_constructor() 方法需要将 A 隐式引用作为其第一个参数,而这里传递的 cls 会成为其第二个参数。
class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        # first we get a reference to the unbound 
        # `A.alternate_constructor` function 
        unbound_func = A.alternate_constructor.im_func
        # now we call it and pass our own `cls` as its first argument
        return unbound_func(cls, *a, **kw)

你的回答中第一段非常棒。我希望官方的Python文档也能这样解释。现在我感觉我对super()有了很好的掌握。 - kitizz

25

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 -> object:
>>> type(b).__mro__   
(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

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

例如,对于c,其祖先链为C -> A -> object
>>> type(c).__mro__   
(<class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

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

例如对于d,其祖先链更有趣,D -> B -> C -> A -> object(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

更多信息

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


3
如果你想知道MRO是如何工作的(即为什么执行从B跳转到C而不是从B跳转到A),请查看此问题的被接受答案:https://dev59.com/Vb7pa4cB1Zd3GeqPrBQr。 基本上,super()将方法调用委托给类型的父类或兄弟类。也就是说,在B中的super()调用将调用委托给C(B的兄弟)而不是A(B的父亲)。 - zardosht
你在实例c的示例中有一个拼写错误: >>> b.m() 应该是 >>> c.m() - doublespaces

12

有很多好答案,但对于视觉学习者:

首先,让我们用参数到超级方法中探索一下,然后再不用。

super inheritance tree example

假设有一个从类 Jack 创建的实例 jack ,其继承链如照片中显示的绿色部分。调用:

super(Jack,jack).method(...)

将使用 jack 的MRO(方法解析顺序)(以特定顺序的继承树),并且将从 Jack 开始搜索。为什么可以提供父类?如果我们从实例 jack 开始搜索,它会找到实例方法,而整个重点是找到其父方法。

如果没有向超级提供参数,则第一个参数就像传递的self的类一样,第二个参数传递的是 self 。在Python3中,这些自动计算。

但是假设我们不想使用 Jack 的方法,而是传入 Jen ,以从 Jen 开始向上搜索该方法。

它一次搜索一层(宽度不是深度),例如,如果 Adam Sue 都具有所需方法,则将首先找到 Sue 的方法。

如果 Cain Sue 都有所需方法,则首先调用 Cain 的方法。

这在代码中对应于:

Class Jen(Cain, Sue):

MRO是从左到右。


4

在多继承的情况下,通常需要调用两个父类的初始化函数,而不仅仅是第一个。super()方法会自动查找下一个继承类,并将当前对象返回为该类的实例,代替直接使用基类。例如:

class Base(object):
    def __init__(self):
        print("initializing Base")

class ChildA(Base):
    def __init__(self):
        print("initializing ChildA")
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        print("initializing ChildB")
        super().__init__()

class Grandchild(ChildA, ChildB):
    def __init__(self):
        print("initializing Grandchild")
        super().__init__()
        
Grandchild()

导致
initializing Grandchild
initializing ChildA
initializing Base

super().__init__()替换Base.__init__(self)会导致

initializing Grandchild
initializing ChildA
initializing ChildB
initializing Base

如所需。


3
这里有一些很好的答案,但它们没有解决如何在层次结构中不同的类具有不同签名的情况下使用super() ... 尤其是在__init__的情况下。为了回答这部分并能够有效地使用super(),我建议阅读我的答案“super()和更改协作方法的签名”。 以下是此场景的解决方案:
  1. 您的层次结构中的顶级类必须继承自类似于SuperObject的自定义类:
  2. 如果类可以接受不同参数,请始终将您收到的所有参数作为关键字参数传递给超级函数,并始终接受**kwargs
class SuperObject:        
    def __init__(self, **kwargs):
        print('SuperObject')
        mro = type(self).__mro__
        assert mro[-1] is object
        if mro[-2] is not SuperObject:
            raise TypeError(
                'all top-level classes in this hierarchy must inherit from SuperObject',
                'the last class in the MRO should be SuperObject',
                f'mro={[cls.__name__ for cls in mro]}'
            )

        # super().__init__ is guaranteed to be object.__init__        
        init = super().__init__
        init()

示例用法:

class A(SuperObject):
    def __init__(self, **kwargs):
        print("A")
        super(A, self).__init__(**kwargs)

class B(SuperObject):
    def __init__(self, **kwargs):
        print("B")
        super(B, self).__init__(**kwargs)

class C(A):
    def __init__(self, age, **kwargs):
        print("C",f"age={age}")
        super(C, self).__init__(age=age, **kwargs)

class D(B):
    def __init__(self, name, **kwargs):
        print("D", f"name={name}")
        super(D, self).__init__(name=name, **kwargs)

class E(C,D):
    def __init__(self, name, age, *args, **kwargs):
        print( "E", f"name={name}", f"age={age}")
        super(E, self).__init__(name=name, age=age, *args, **kwargs)

E(name='python', age=28)

输出:

E name=python age=28
C age=28
A
D name=python
B
SuperObject

2
考虑以下代码:
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

在编写XY时,PQ甚至可能来自另一个文件。因此,基本上在编写class Y(X)时,您不知道super(Child, self)将引用什么,即使Y的签名与Y(X)一样简单。这就是为什么使用super可能是更好的选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接