使用__init__()方法理解Python Super()

为什么使用super()

使用Base.__init__super().__init__有区别吗?

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()
2381237 次浏览

实际上没有。super()查看MRO中的下一个类(方法解析顺序,使用cls.__mro__访问)来调用方法。只需调用base__init__就会调用base__init__。碰巧的是,MRO只有一个项目——base。所以你真的在做完全相同的事情,但对super()来说是更好的方式(特别是如果你稍后会遇到多重继承)。

super()允许您避免显式引用基类,这可能很好。但主要优势来自多重继承,其中各种有趣的东西都可能发生。如果您还没有,请参阅超级标准文档

请注意Python 3.0中的语法更改:您可以只说super().__init__()而不是super(ChildB, self).__init__(),这在IMO中要好得多。标准文档还引用了使用指南#2,这非常具有解释性。

对于Python 2.7,我相信自从super()在2.2版中引入以来,只有当父类之一从最终继承object新式课堂)的类继承时,您才能调用#0

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

超级没有副作用

Base = ChildB
Base()

按预期工作

Base = ChildA
Base()

进入无限递归。

值得注意的是,在Python 3.0+中,您可以使用

super().__init__()

进行调用,这很简洁,不需要您显式引用父或类名,这很方便。我只想补充一点,对于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的子类。假设我不想为Square编写单独的构造函数,因为Rectangle的构造函数足够好,但无论出于什么原因,我想实现一个Square,这样我就可以重新实现一些其他方法。

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

我试着去理解super()

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

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

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

在Python 2中,我们被要求像这样用定义类的名称和self调用super,但从现在开始我们将避免这种情况,因为它是冗余的,更慢(由于名称查找),更冗长(所以如果你还没有更新你的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__()

这段代码的主要区别在于,在ChildB中,您在__init__中获得了一个带有super的间接层,它使用定义它的类来确定要在MRO中查找的下一个类的__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)breakcheck_next += 1

写得更像原生Python:

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

如果我们没有super对象,我们必须在任何地方编写此手动代码(或重新创建它!)以确保我们按照方法解析顺序调用正确的next方法!

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

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

因为它需要MRO的第一个参数,将#0与静态方法一起使用是不可能的,因为它们无法访问调用它们的类的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__()

让我们创建一个依赖项,我们希望在Children之后调用它:

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

请记住,ChildB使用超级,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'edChildA init'edBase init'ed<__main__.UserA object at 0x0000000003403BA8>

但是UserB确实调用了UserDependency,因为ChildB调用了super

>>> UserB()UserB init'edChildB init'edUserDependency init'edBase init'ed<__main__.UserB object at 0x0000000003403438>

对另一个答案的批评

在任何情况下都不应该执行以下操作,另一个答案建议,因为当你子类ChildB时肯定会出错:

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

(这个答案并不聪明,也不是特别有趣,但尽管评论中有直接的批评和超过17次的否决,回答者坚持建议,直到一位好心的编辑解决了他的问题。

说明:使用self.__class__代替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()调用方法没有参数,允许我们回避这个问题。

主要区别在于ChildA.__init__将无条件地调用Base.__init__,而ChildB.__init__将调用__init__中的,无论类恰好是self祖先中的ChildB祖先(这可能与你所期望的不同)。

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

class Mixin(Base):def __init__(self):print "Mixin stuff"super(Mixin, self).__init__()
class ChildC(ChildB, Mixin):  # Mixin is now between ChildB and Basepass
ChildC()help(ChildC) # shows that the Method Resolution Order is ChildC->ChildB->Mixin->Base

然后#0不再是#1的父节点ChildC实例。如果selfChildC实例,现在super(ChildB, self)将指向Mixin

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

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

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