2953

我试图了解super(). 从外观上看,两个子类都可以创建,就好了。

我很想知道以下 2 个子类之间的实际区别。

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

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()

ChildA() 
ChildB()
4

7 回答 7

2148

super()让您避免显式引用基类,这很好。但主要优势在于多重继承,可以发生各种有趣的事情。如果您还没有,请参阅super 上的标准文档。

请注意,Python 3.0 中的语法发生了变化super().__init__():您可以说super(ChildB, self).__init__()哪个 IMO 更好一些。标准文档还参考了使用指南,super()该指南非常易于解释。

于 2009-02-23T00:37:31.220 回答
1041

我试图理解super()

我们使用的原因super是,可能使用协作多重继承的子类将在方法解析顺序 (MRO) 中调用正确的下一个父类函数。

在 Python 3 中,我们可以这样称呼它:

class ChildB(Base):
    def __init__(self):
        super().__init__()

在 Python 2 中,我们需要super像这样使用定义类的名称和来调用self,但从现在开始我们将避免这种情况,因为它是多余的、较慢的(由于名称查找)和更冗长的(所以如果你更新你的 Python还没有!):

        super(ChildB, self).__init__()

如果没有 super,您使用多重继承的能力会受到限制,因为您硬连线下一个父级的调用:

        Base.__init__(self) # Avoid this.

我在下面进一步解释。

“这段代码实际上有什么区别?:”

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super().__init__()

此代码的主要区别在于,您在with中ChildB获得了一个间接层,它使用定义它的类来确定要在 MRO 中查找的下一个类。__init__super__init__

我在规范问题的答案中说明了这种差异,如何在 Python 中使用“超级”?,它演示了依赖注入协作多重继承

如果 Python 没有super

这里的代码实际上非常接近super(它是如何在 C 中实现的,减去一些检查和回退行为,然后翻译成 Python):

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()
        check_next = mro.index(ChildB) + 1 # next after *this* class.
        while check_next < len(mro):
            next_class = mro[check_next]
            if '__init__' in next_class.__dict__:
                next_class.__init__(self)
                break
            check_next += 1

写得更像原生 Python:

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()
        for next_class in mro[mro.index(ChildB) + 1:]: # slice to end
            if hasattr(next_class, '__init__'):
                next_class.__init__(self)
                break

如果我们没有该super对象,我们将不得不在任何地方编写此手动代码(或重新创建它!)以确保我们在方法解析顺序中调用正确的下一个方法!

super 如何在 Python 3 中做到这一点,而不被明确告知调用它的方法中的哪个类和实例?

它获取调用堆栈帧,并找到类(隐式存储为局部自由变量,__class__使调用函数成为类的闭包)和该函数的第一个参数,它应该是通知它哪个实例或类要使用的方法解析顺序 (MRO)。

由于它需要 MRO 的第一个参数,因此无法使用super静态方法,因为它们无法访问调用它们的类的 MRO

对其他答案的批评:

super() 让您避免显式引用基类,这很好。. 但主要优势在于多重继承,可以发生各种有趣的事情。如果您还没有,请参阅 super 上的标准文档。

它相当随意,并没有告诉我们太多,但重点super不是避免编写父类。重点是确保调用方法解析顺序 (MRO) 中的下一个方法。这在多重继承中变得很重要。

我会在这里解释。

class Base(object):
    def __init__(self):
        print("Base init'ed")

class ChildA(Base):
    def __init__(self):
        print("ChildA init'ed")
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        print("ChildB init'ed")
        super().__init__()

让我们创建一个我们希望在 Child 之后调用的依赖项:

class UserDependency(Base):
    def __init__(self):
        print("UserDependency init'ed")
        super().__init__()

现在请记住,ChildB使用 super,ChildA而不是:

class UserA(ChildA, UserDependency):
    def __init__(self):
        print("UserA init'ed")
        super().__init__()

class UserB(ChildB, UserDependency):
    def __init__(self):
        print("UserB init'ed")
        super().__init__()

并且UserA不调用 UserDependency 方法:

>>> UserA()
UserA init'ed
ChildA init'ed
Base init'ed
<__main__.UserA object at 0x0000000003403BA8>

UserB实际上是否调用 UserDependency 因为ChildB调用super

>>> UserB()
UserB init'ed
ChildB init'ed
UserDependency init'ed
Base init'ed
<__main__.UserB object at 0x0000000003403438>

批评另一个答案

在任何情况下,您都不应该执行以下操作,另一个答案表明,当您将 ChildB 子类化时,您肯定会遇到错误:

super(self.__class__, self).__init__()  # DON'T DO THIS! EVER.

(这个答案不是很聪明也不是特别有趣,但是尽管评论中有直接的批评和超过 17 票反对,但回答者坚持提出建议,直到一位善良的编辑解决了他的问题。)

解释:使用self.__class__in 代替类名super()会导致递归。super让我们在 MRO(请参阅此答案的第一部分)中查找子类的下一个父级。如果您告诉super我们在子实例的方法中,那么它将查找行中的下一个方法(可能是这个方法)导致递归,可能导致逻辑失败(在回答者的示例中,确实如此)或RuntimeError递归深度超过。

>>> class Polygon(object):
...     def __init__(self, id):
...         self.id = id
...
>>> class Rectangle(Polygon):
...     def __init__(self, id, width, height):
...         super(self.__class__, self).__init__(id)
...         self.shape = (width, height)
...
>>> class Square(Rectangle):
...     pass
...
>>> Square('a', 10, 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
TypeError: __init__() missing 2 required positional arguments: 'width' and 'height'

幸运的是, Python 3 的super()不带参数的新调用方法使我们能够回避这个问题。

于 2014-11-25T19:00:33.520 回答
270

已经注意到,在 Python 3.0+ 中,您可以使用

super().__init__()

进行调用,这很简洁,不需要您显式引用父 OR 类名称,这很方便。我只是想为 Python 2.7 或更低版本添加它,有些人通过编写self.__class__而不是类名来实现名称不敏感的行为,即

super(self.__class__, self).__init__()  # DON'T DO THIS!

super但是,这会中断对从您的类继承的任何类的调用,其中self.__class__可能返回子类。例如:

class Polygon(object):
    def __init__(self, id):
        self.id = id

class Rectangle(Polygon):
    def __init__(self, id, width, height):
        super(self.__class__, self).__init__(id)
        self.shape = (width, height)

class Square(Rectangle):
    pass

这里我有一个类Square,它是 的子类Rectangle。假设我不想为 for 编写单独Square的构造函数,因为构造函数 forRectangle已经足够好了,但无论出于何种原因,我都想实现一个 Square,这样我就可以重新实现一些其他方法。

当我创建SquareusingmSquare = Square('a', 10,10)时,Python 调用构造函数 forRectangle因为我没有给出Square它自己的构造函数。但是,在 for 的构造函数中Rectangle,调用super(self.__class__,self)将返回 的超类mSquare,因此它Rectangle再次调用了 for 的构造函数。正如@S_C 所提到的,这就是无限循环的发生方式。在这种情况下,当我运行时,super(...).__init__()我正在调用构造函数,Rectangle但由于我没有给它任何参数,我会得到一个错误。

于 2013-10-08T20:08:39.057 回答
82

超级没有副作用

Base = ChildB

Base()

按预期工作

Base = ChildA

Base()

进入无限递归。

于 2012-11-27T23:26:47.940 回答
74

只是提醒一下......对于 Python 2.7,我相信自从super()在 2.2 版中引入以来,您只能super()在其中一个父级继承自最终继承的类object新型类)时调用。

就个人而言,至于 python 2.7 代码,我将继续使用BaseClassName.__init__(self, args),直到我真正获得使用super().

于 2012-05-25T17:52:49.797 回答
57

没有,真的。super()查看 MRO 中的下一个类(方法解析顺序,使用 访问cls.__mro__)来调用方法。只需调用 base__init__即可调用 base __init__。碰巧的是,MRO 只有一个项目——基础。所以你真的在做同样的事情,但是以一种更好的方式super()(特别是如果你稍后进入多重继承)。

于 2009-02-23T00:34:57.840 回答
37

主要区别在于ChildA.__init__它将无条件地调用Base.__init__,而ChildB.__init__将调用恰好是祖先行中的祖先的__init__任何类 (这可能与您的期望不同)。ChildBself

如果添加一个ClassC使用多重继承的:

class Mixin(Base):
  def __init__(self):
    print "Mixin stuff"
    super(Mixin, self).__init__()

class ChildC(ChildB, Mixin):  # Mixin is now between ChildB and Base
  pass

ChildC()
help(ChildC) # shows that the Method Resolution Order is ChildC->ChildB->Mixin->Base

thenBase不再是ChildBforChildC实例的父级。现在super(ChildB, self)将指向Mixinifself是一个ChildC实例。

Mixin您已在ChildB和之间插入Base。你可以利用它super()

因此,如果您将类设计为可以在协作多重继承场景中使用,那么您使用这些类super是因为您并不真正知道谁将成为运行时的祖先。

超级考虑的超级帖子pycon 2015 随附的视频很好地解释了这一点。

于 2015-09-21T06:41:03.070 回答