通过__init __()方法了解 Python super()

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

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

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

答案

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

请注意, Python 3.0的语法已更改 :您可以只说super().__init__()而不是super(ChildB, self).__init__() ,这对 IMO 来说要好得多。标准文档还参考了使用super()指南,这是非常说明性的。

我正在尝试了解super()

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

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

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

在 Python 2 中,我们需要像这样使用它:

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(ChildB, self).__init__()
        # super().__init__() # you can call super like this in Python 3!

这段代码的主要区别在于,您可以在带有super__init__获得一个间接层,该层使用当前类来确定要在 MRO 中查找的下一类的__init__

我在一个标准问题的答案( 如何在 Python 中使用 “超级”)中说明了这种差异 ,证明了依赖注入合作多重继承

如果 Python 没有super

这是实际上与super等效的代码(如何用 C 实现,减去一些检查和后备行为,并转换为 Python):

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()             # Get the Method Resolution Order.
        check_next = mro.index(ChildB) + 1 # Start looking 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对象,则必须在任何地方编写(或重新创建!)此手动代码,以确保我们在 “方法解析顺序” 中调用正确的下一个方法!

在 Python 3 中 super 如何做到这一点,而又没有明确地告诉它调用方法的哪个类和实例?

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

由于它需要 MRO 的第一个参数,因此无法将super与静态方法一起使用

对其他答案的批评:

通过 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(ChildB, self).__init__()

然后创建一个要在 Child 之后调用的依赖项:

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

现在记住, ChildB使用超级, ChildA不使用:

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

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

而且UserA不调用 UserDependency 方法:

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

但是UserB ,因为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 次的否决,回答者仍然坚持建议,直到善良的编辑解决了他的问题。)

说明:该答案建议像这样调用 super:

super(self.__class__, self).__init__()

这是完全错误的。 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.0 + 中,您可以使用

super().__init__()

进行调用,这很简洁,不需要您显式地引用父级或类名,这很方便。我只想添加它,对于 Python 2.7 或更低版本,可以通过编写self.__class__而不是类名来获得这种名称不敏感的行为,即

super(self.__class__, self).__init__()

但是,这会中断对从您的类继承的任何类的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的子类。说我不想为Square写一个单独的构造函数,因为Rectangle的构造函数已经足够好了,但是出于任何原因,我都想实现一个 Square,以便可以重新实现其他方法。

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

超级没有副作用

Base = ChildB

Base()

按预期工作

Base = ChildA

Base()

进入无限递归。

刚开始…… 使用 Python 2.7,并且我相信自从在版本 2.2 中引入super()以来,如果其中一个父类继承自最终继承object的类( 新样式类 super()则只能调用super() )。

就个人而言,对于 python 2.7 代码,我将继续使用BaseClassName.__init__(self, args)直到我真正获得了使用super()的优势为止。

真的没有。 super()查看 MRO 中的下一个类(方法解析顺序,使用cls.__mro__访问)以调用方法。只需调用基本__init__调用基本__init__ 。碰巧的是,MRO 只有一项 - 基础。因此,您实际上确实在做完全相同的事情,但是使用super()方式更好(特别是如果以后要进行多重继承的话)。

主要区别在于, self的祖先行中,恰好是ChildB祖先的任何类中ChildA.__init__都会无条件调用Base.__init__ChildB.__init__会调用__init__ (可能与您的预期不同)。

如果添加使用多重继承的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 the Method Resolution Order is ChildC->ChildB->Mixin->Base

那么Base不再是 ChildC实例ChildB的父 ChildC 。现在super(ChildB, self)如果selfChildC实例super(ChildB, self)将指向Mixin

您已经在ChildBBase之间插入了Mixin 。您可以通过super()来利用它

因此,如果您将类设计为可以在 “协作式多重继承” 方案中使用,请使用super因为您实际上并不知道在运行时谁将成为祖先。

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