提问




    这个问题在这里已有答案:

    

            

  •             超级在Python中做了什么?
                    
                        6个答案
                    
            

  •     

最佳参考


super()允许你明确地避免引用基类,这可能很好。但主要优势在于多重继承,可以发生各种有趣的事情。如果你还没有,请参阅super上的标准文档。/questions/222877/what-does-super-do-in-python [91] [92]


请注意,Python 3.0中的语法发生了变化:您可以说super().__init__()而不是super(ChildB, self).__init__()哪个IMO更好一些。[93]

其它参考1



  

我试图理解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获得了一个间接层,它使用当前类来确定下一个类[[s __init__来查找MRO。


我在一个规范问题的答案中说明了这个差异,如何在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中执行此操作?


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


因为它需要MRO的第一个参数,所以使用super和静态方法是不可能的。[95]


对其他答案的批评:




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


说明:那个回答建议像这样调用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'

其它参考2


有人指出,在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,所以我可以重新实现其他方法。


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

其它参考3


超级没有副作用


Base = ChildB

Base()


按预期工作


Base = ChildA

Base()


进入无限递归。

其它参考4


只是一个抬头...用Python 2.7,我相信自从super()在版本2.2中引入以来,如果其中一个父级继承自最终继承super()的类,则只能调用super() object(新式课程)。[96] [97]


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

其它参考5


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

其它参考6


主要区别在于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不再是ChildC实例的ChildB 的父级。现在super(ChildB, self)将指向Mixin如果selfChildC实例。


您已在ChildBBase之间插入Mixin。你可以利用super()来利用它


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


超级考虑的超级帖子和pycon 2015伴随视频解释了这一点。 [98] [99]