为什么使用super()?

使用Base有区别吗__init__和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()

当前回答

超级没有副作用

Base = ChildB

Base()

按预期工作

Base = ChildA

Base()

进入无限递归。

其他回答

超级没有副作用

Base = ChildB

Base()

按预期工作

Base = ChildA

Base()

进入无限递归。

只是抬头。。。对于Python 2.7,我相信自从super()在2.2版中引入以来,只有当父类之一继承了最终继承对象的类(新型类)时,才能调用super(。

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

主要区别在于ChildA__init__将无条件调用Base__init__而ChildB__init__将在任何类中调用__init__,该类恰好是自己祖先行中的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 Base
  pass

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

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

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

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

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

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中,我们需要使用定义类的名称和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中使用“super”?”的回答中说明了这一区别?,它展示了依赖注入和协作多重继承。

如果Python没有超级

这里的代码实际上与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如何在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__作为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'

幸运的是,Python3新的super()调用方法没有参数,这让我们可以避开这个问题。