使用__init __()方法理解Python super()

时间:2009-02-23 00:30:09

标签: python class oop inheritance 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()

7 个答案:

答案 0 :(得分:1615)

super()可以避免显式引用基类,这可能很好。但主要优势在于多重继承,其中可能发生各种fun stuff。如果您还没有,请参见standard docs on super

请注意the syntax changed in Python 3.0:您可以说super().__init__()而不是super(ChildB, self).__init__()哪个IMO更好一点。标准文档也引用guide to using super(),这是非常明确的解释。

答案 1 :(得分:573)

  

我试图了解super()

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

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

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

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

        super(ChildB, self).__init__()

如果没有超级,你在使用多重继承的能力上受到限制:

        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!

此代码的主要区别在于,您在__init__中使用super获得了一个间接层,该层使用当前类来确定下一个类__init__查阅MRO。

我在canonical question, How to use 'super' in 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对象,我们必须在任何地方编写此手动代码(或重新创建它!)以确保我们在方法分辨率中调用正确的下一个方法订购!

super如何在没有明确告知调用它的方法中的哪个类和实例的情况下在Python 3中执行此操作?

它获取调用堆栈帧,并找到类(隐式存储为本地自由变量__class__,使调用函数成为类的闭包)和该函数的第一个参数,应该是通知它使用哪个Method Resolution Order(MRO)的实例或类。

因为它需要MRO的第一个参数using super with static methods is impossible

对其他答案的批评:

  

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个downvotes,回答者坚持提出建议,直到一位善良的编辑解决了他的问题。) < / p>

说明:那个回答建议像这样调用超级:

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

完全错误。 super让我们在子类中查找MRO中的下一个父级(请参阅本答案的第一部分)。如果你告诉super我们在子实例的方法中,那么它将在行中查找下一个方法(可能是这个)导致递归,可能导致逻辑失败(在回答者& #39;例如,它确实)或超过递归深度的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'

答案 2 :(得分:240)

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

super().__init__()

进行调用,这是简洁的,不需要您明确引用父OR类名称,这可能很方便。我只想在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,所以我可以重新实现其他方法。

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

答案 3 :(得分:80)

超级没有副作用

Base = ChildB

Base()

按预期工作

Base = ChildA

Base()

进入无限递归。

答案 4 :(得分:71)

只是提前......使用Python 2.7,我相信自从版本2.2中引入了super()以来,如果其中一个父级继承自最终的类,则只能调用super()继承objectnew-style classes)。

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

答案 5 :(得分:50)

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

答案 6 :(得分:28)

主要区别在于ChildA.__init__将无条件地调用Base.__init__,而ChildB.__init__会在中调用__init__,无论哪个类恰好是ChildB祖先self的祖先系列 (可能与您的预期不同)。

如果添加使用多重继承的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不再是ChildB实例的ChildC 的父级。如果super(ChildB, self)Mixin个实例,则self会指向ChildC

您已在MixinChildB之间插入了Base。你可以利用super()

来利用它

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

super considered super postpycon 2015 accompanying video很好地解释了这一点。