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

2022-09-05 00:46:07

为什么使用?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()

答案 1

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

请注意,Python 3.0中的语法发生了变化:你可以说哪个IMO更好。标准文档还引用了使用 super() 的指南,这非常具有解释性。super().__init__()super(ChildB, self).__init__()


答案 2

我试图理解super()

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

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

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

在 Python 2 中,我们被要求像这样使用定义类的名称和 调用,但从现在开始我们将避免这种情况,因为它是冗余的,较慢的(由于名称查找),并且更详细(因此,如果您还没有更新Python!):superself

        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__()

此代码中的主要区别在于,在 with 中获得一个间接层,该层使用定义它的类来确定要在 MRO 中查找的下一个类。ChildB__init__super__init__

我在规范问题的回答中说明了这种差异,如何在Python中使用“super”?,它演示了依赖注入协作多重继承

如果Python没有super

以下代码实际上与(它在C中实现的方式,减去一些检查和回退行为,并转换为Python)非常接近:super

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

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

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

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

由于它需要 MRO 的第一个参数,因此将 super 与静态方法结合使用是不可能的,因为它们无法访问调用它们的类的 MRO

对其他答案的批评:

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

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

我会在这里解释。

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__()

让我们创建一个依赖关系,我们希望在孩子之后调用它:

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

现在记住,使用超级,不:ChildBChildA

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__()

并且不调用 UserDependency 方法:UserA

>>> UserA()
UserA init'ed
ChildA init'ed
Base init'ed
<__main__.UserA object at 0x0000000003403BA8>

但实际上确实调用了 UserDependency,因为 调用 :UserBChildBsuper

>>> 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次的反对票,但回答者坚持建议它,直到一位善良的编辑解决了他的问题。

说明: 用作 中类名的替代品将导致递归。 让我们在 MRO 中查找子类的下一个父类(请参阅本答案的第一部分)。如果你告诉我们在子实例的方法中,它将在行中查找下一个方法(可能是这个),导致递归,这可能会导致逻辑故障(在回答者的例子中,它确实如此)或超过递归深度。self.__class__super()supersuperRuntimeError

>>> 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()