680

有什么区别:

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

和:

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

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

4

11 回答 11

408

有什么不同?

SomeBaseClass.__init__(self) 

意思是打电话给SomeBaseClass's __init__。尽管

super().__init__()

表示从实例的方法解析顺序 (MRO)__init__中跟随 的子类(定义此方法的那个)的父类调用绑定。SomeBaseClass

如果该实例是该子类的类,则 MRO 中可能会有不同的父类。

简单解释

当您编写一个类时,您希望其他类能够使用它。super()使其他类更容易使用您正在编写的类。

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

super()可以启用这种架构。

当另一个类继承您编写的类时,它也可能是从其他类继承的。并且这些类可以根据类的顺序__init__在此之后有一个用于方法解析的类。__init__

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

如果您编写自己的代码供个人使用,您可能不会关心这种区别。但是,如果您希望其他人使用您的代码,那么使用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不带参数的调用。

具有前向兼容性的间接

它给了你什么?对于单继承,从静态分析的角度来看,问题中的示例实际上是相同的。但是, usingsuper为您提供了一层具有前向兼容性的间接性。

前向兼容性对于经验丰富的开发人员来说非常重要。您希望您的代码在更改时保持最小的更改。当您查看您的修订历史时,您希望准确了解何时发生了哪些变化。

您可以从单一继承开始,但如果您决定添加另一个基类,您只需更改与基类的行 - 如果基类在您继承的类中发生更改(例如添加了 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

使用 un-super 子级无法注入依赖项,因为您使用的子级已硬编码要在其自身之后调用的方法:

>>> 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来引用父类,而不是对其进行硬编码。

您打算引用的父类是next-in-line,而不是您看到子继承的父类。

不使用super会对代码的用户施加不必要的限制。

于 2015-11-02T00:53:24.423 回答
347

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

但是,几乎不可能在没有super(). 这包括常见的习惯用法,如 mixins、接口、抽象类等。这会扩展到以后扩展您的代码。如果后来有人想编写一个扩展类Child和一个 mixin,他们的代码将无法正常工作。

于 2008-10-21T18:24:50.463 回答
52

我玩了一点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. 新式课堂的内幕
于 2016-12-29T17:31:09.887 回答
35

这一切不都假设基类是新式类吗?

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)

于 2008-10-22T00:06:32.333 回答
22

当调用super()以解析到父类方法、实例方法或静态方法的版本时,我们希望将我们所在范围的当前类作为第一个参数传递,以指示我们尝试解析到哪个父类的范围,并作为第二个参数是感兴趣的对象,以指示我们试图将该范围应用于哪个对象。

考虑一个类层次结构A, B, 并且C每个类都是其后一个类的父级, 和a,b以及每个类的c相应实例。

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

使用super静态方法

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

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与实例方法一起使用

例如super()从内部使用__init__()

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-当super()在内部调用时,__init__()我们将子类作为第一个参数传递,将感兴趣的对象作为第二个参数传递,这通常是对子类实例的引用。

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与类方法一起使用

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-当在类方法中调用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)
于 2016-09-07T17:33:28.780 回答
16

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,没有超级调用:

>>> 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'>)

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

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

>>> 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 分钟视频

于 2020-12-20T19:33:51.387 回答
11

许多很好的答案,但对于视觉学习者:首先让我们用参数来探索 super,然后不用。 超级继承树示例

想象有一个jack从类创建的实例Jack,它具有图中绿色所示的继承链。来电:

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

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

如果不向 super 提供参数,则传入的第一个参数是 的类self,传入的第二个参数是self。这些是在 Python3 中为您自动计算的。

但是,如果我们不想使用Jack' 方法,而不是传入Jack,我们可以传入Jen以开始向上搜索 from 的方法Jen

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

如果Cain两者Sue都有所需的方法,Cain则将首先调用 ' 的方法。这在代码中对应于:

Class Jen(Cain, Sue):

MRO 从左到右。

于 2019-06-24T17:25:47.187 回答
3

在多重继承的情况下,您通常希望调用父母双方的初始化程序,而不仅仅是第一个。super() 并不总是使用基类,而是查找方法解析顺序 (MRO) 中的下一个类,并将当前对象作为该类的实例返回。例如:

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

Base.__init__(self)super().__init__()结果替换

initializing Grandchild
initializing ChildA
initializing ChildB
initializing Base

如预期的。

于 2020-07-31T15:05:54.150 回答
2

这里有一些很好的答案,但他们没有解决如何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
于 2019-06-22T11:26:05.743 回答
1

考虑以下代码:

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()

如果更改Yto的构造函数X.__init__,您将得到:

X
Y
Q

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

X
P
Y
Q

并且P甚至Q可能涉及另一个您在编写时不知道的文件XY. 所以,基本上,你不会知道super(Child, self)你在写的时候会引用什么class Y(X),即使是 Y 的签名也很简单Y(X)。这就是为什么 super 可能是更好的选择。

于 2020-05-17T13:55:34.257 回答
0
class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

这很容易理解。

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

好的,如果你现在使用 会发生什么super(Child,self)

在创建 Child 实例时,其 MRO(Method Resolution Order) 基于继承的顺序为 (Child, SomeBaseClass, object)。(假设 SomeBaseClass 除了默认对象之外没有其他父对象)

通过Child, self, super在实例的 MRO 中搜索self,返回 Child 的 next 代理对象,本例为 SomeBaseClass,然后该对象调用__init__SomeBaseClass 的方法。换句话说,如果它是super(SomeBaseClass,self),则返回的代理对象super将是object

对于多继承,MRO 可以包含许多类,因此基本上super可以让您决定要在 MRO 中的哪个位置开始搜索。

于 2018-07-17T19:14:11.403 回答