协慌网

登录 贡献 社区

使用__init __()方法理解 Python 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()

答案

super()可以避免显式引用基类,这可能很好。但主要优势在于多重继承,可以发生各种有趣的事情。如果您还没有,请参阅super 上标准文档

请注意, Python 3.0的语法发生了变化 :你可以说super().__init__()而不是super(ChildB, self).__init__() ,IMO 非常好。

我想了解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获得了一个间接层,它使用当前类来确定要在 MRO 中查找的下一个类的__init__

我在规范问题的答案中说明了这种差异,如何在 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和静态方法是不可能的

对其他答案的批评:

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

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

已经注意到在 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的构造函数,但由于我没有给它任何参数,我将得到一个错误。