“超级”在Python中有什么作用?-超级().__init__()和显式超类__init__()之间的区别

有什么区别:

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

和:

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

我已经看到super在只有单继承的类中被大量使用。我明白为什么你会在多重继承中使用它,但不清楚在这种情况下使用它有什么好处。

276628 次浏览

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

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

所有这些不都假设基类是一个新式类吗?

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)

有什么区别?

SomeBaseClass.__init__(self)

表示调用SomeBaseClass__init__。而

super().__init__()

表示在实例的方法解析顺序(MRO)中从SomeBaseClass的子类(定义此方法的子类)之后的父类调用绑定的__init__

如果实例是这个子类的子类,则MRO中接下来可能会有不同的父类。

简单地解释

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

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

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

当另一个类对你编写的类进行子类化时,它也可能从其他类继承。这些类可能有一个__init__,它位于这个__init__之后,这取决于用于方法解析的类的顺序。

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

如果您正在编写自己的代码供个人使用,您可能不在乎这种区别。但如果您希望其他人使用您的代码,使用super是为代码用户提供更大灵活性的一件事。

Python 2 vs 3

这适用于Python 2和3:

super(Child, self).__init__()

这仅适用于Python 3:

super().__init__()

它通过在堆栈框架中向上移动并获取方法的第一个参数(通常self用于实例方法或cls用于类方法-但可能是其他名称)并在自由变量中找到类(例如Child)来处理没有参数的情况(它以名称__class__作为方法中的自由闭包变量进行查找)。

我以前更喜欢演示使用super的交叉兼容方式,但现在Python 2在很大程度上已被弃用,我将演示Python 3的做事方式,即不带参数调用super

具有前向兼容性的间接

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

向前兼容性对于经验丰富的开发人员来说非常重要。您希望您的代码在更改时保持最小的更改。当您查看修订历史时,您希望准确地看到什么时候更改了。

你可以从单继承开始,但是如果你决定添加另一个基类,你只需要改变带有基类的行——如果你继承的类中的基类改变了(比如添加了一个混合),你在这个类中什么也不会改变。

在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可能会对代码的用户施加不必要的限制。

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

考虑一个类层次结构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

使用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__也将返回一个静态方法,并且需要显式提供所有参数,包括对insterest对象的引用,在本例中为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与类方法一起使用

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)

我玩过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. 新式类的内幕
class Child(SomeBaseClass):
def __init__(self):
SomeBaseClass.__init__(self)

这是相当容易理解的。

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

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

当创建子实例时,它的MRO(方法解析顺序)是基于继承的顺序(子、部分类、对象)。(假设除默认对象外,部分类没有其他父类)

通过传递Child, selfsuperself实例的MRO中进行搜索,并返回Children旁边的代理对象,在这种情况下它是某些基础类,该对象然后调用某些基础类的__init__方法。换句话说,如果它是super(SomeBaseClass,self)super返回的代理对象将是object

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

这里有一些很好的答案,但它们没有解决如何在层次结构中的不同类具有不同签名的情况下使用super()

要回答这部分,并能够有效地使用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

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

想象一下,从类Jack创建了一个实例jack,它的继承链如图中绿色所示。调用:

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

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

如果不提供参数,就像传入的第一个参数是self的类,传入的第二个参数是self。这些是Python3中自动计算的。

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

它一次搜索一层(宽度而不是深度),例如,如果AdamSue都有所需的方法,则会首先找到Sue中的那个。

如果CainSue都有所需的方法,Cain的方法将首先被调用。 这对应于代码:

Class Jen(Cain, Sue):

MRO从左到右。

考虑以下代码:

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

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

在多重继承的情况下,你通常希望调用父母双方的初始化器,而不仅仅是第一个。而不是总是使用基类,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

如你所愿。

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