class:使用_init__()方法理解Python super()

本问题已经有最佳答案,请猛点这里访问。

我试图理解super()的用法。从它的外观来看,可以创建两个子类,这很好。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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__(),在我看来,这样更好。标准文档还引用了一个使用super()的指南,该指南非常具有解释性。


I'm trying to understand super()

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

在python3中,我们可以这样调用它:

1
2
3
class ChildB(Base):
    def __init__(self):
        super().__init__()

在python2中,我们需要这样使用它:

1
        super(ChildB, self).__init__()

如果没有super,你使用多重继承的能力就会受到限制:

1
        Base.__init__(self) # Avoid this.

我在下面进一步解释。

"What difference is there actually in this code?:"

1
2
3
4
5
6
7
8
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的间接层,它使用当前类来确定要在MRO中查找的下一个类的__init__

我在一个典型问题的答案中说明了这种差异,即如何在Python中使用"super"?,演示依赖项注入和协作多重继承。

如果Python没有super

下面的代码实际上与super非常相似(它是如何在C语言中实现的,减去一些检查和回退行为,并翻译成Python):

1
2
3
4
5
6
7
8
9
10
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:

1
2
3
4
5
6
7
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对象,我们将不得不到处编写这个手工代码(或者重新创建它!),以确保我们按照方法解析顺序调用正确的next方法!

在python3中,super是如何做到这一点的,而不需要显式地告诉它是从哪个方法调用的类和实例?

调用堆栈帧,并发现类(隐式存储为当地自由变量,__class__,使调用函数闭包的类)和函数的第一个参数,应通知它的实例或类使用哪个方法解析顺序(MRO)。

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

对其他答案的批评:

super() lets you avoid referring to the base class explicitly, which can be nice. . But the main advantage comes with multiple inheritance, where all sorts of fun stuff can happen. See the standard docs on super if you haven't already.

它相当手摇,没有告诉我们太多,但是super的重点不是避免编写父类。重点是确保调用方法解析顺序(MRO)中的下一个方法。这在多重继承中变得很重要。

我将解释。

1
2
3
4
5
6
7
8
9
10
11
12
13
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__()

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

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

现在请记住,ChildB使用super, ChildA不使用:

1
2
3
4
5
6
7
8
9
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方法:

1
2
3
4
5
>>> UserA()
UserA init'ed
ChildA init'
ed
Base init'ed
<__main__.UserA object at 0x0000000003403BA8>

但是UserB,因为ChildB使用super,所以使用!:

1
2
3
4
5
6
>>> UserB()
UserB init'ed
ChildB init'
ed
UserDependency init'ed
Base init'
ed
<__main__.UserB object at 0x0000000003403438>

批评另一个答案

在任何情况下都不应该这样做,这是另一个答案的建议,因为子类化ChildB时肯定会出错:

1
        super(self.__class__, self).__init__() # Don't do this. Ever.

(这个答案既不聪明也不特别有趣,但是尽管在评论中有直接的批评,而且有超过17次的反对票,这个答案还是坚持建议,直到一位好心的编辑解决了他的问题。

解释:这个答案建议这样称呼super:

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

这是完全错误的。super让我们在MRO中查找子类的下一个父类(请参阅这个答案的第一部分)。如果您告诉super我们在子实例的方法中,那么当超过递归深度时,它将在行中查找下一个方法(可能是这个),从而导致递归,也可能导致逻辑失败(在answerer的示例中,确实如此)或RuntimeError

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> 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__而不是类名来获得这种名称不敏感的行为,即

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

但是,这中断了对继承自您的类的任何类的super的调用,其中self.__class__可以返回子类。例如:

1
2
3
4
5
6
7
8
9
10
11
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的构造函数已经足够好了,但是无论出于什么原因,我想实现一个正方形,这样我就可以重新实现其他一些方法。

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


Super没有副作用

1
2
3
Base = ChildB

Base()

像预期的那样工作

1
2
3
Base = ChildA

Base()

进入无限递归。


请注意……使用Python 2.7,而且我相信自从在2.2版本中引入了super()以来,您只能在父类中有一个继承了最终继承了object(新样式类)的类时调用super()

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


没有,真的。super()查看MRO(方法解析顺序,用cls.__mro__访问)中的下一个类来调用方法。只要调用base __init__就会调用base __init__。碰巧,MRO只有一个项——基。因此,您实际上是在做完全相同的事情,但是使用super()的方式更好(特别是在稍后讨论多重继承时)。


主要的区别是ChildA.__init__将无条件地调用Base.__init__,而ChildB.__init__将调用__init__,无论在self的祖先行中发生了什么情况(这可能与您的预期不同)。

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

1
2
3
4
5
6
7
8
9
10
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

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

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

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

super considered的super post和pycon 2015的视频很好地解释了这一点。