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

原文由 Mizipzor 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 349
2 个回答

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

请注意, Python 3.0 中的语法发生了变化:您可以只说 super().__init__() 而不是 super(ChildB, self).__init__() IMO 更好一些。标准文档还参考了使用 指南 super() 非常具有解释性。

原文由 Kiv 发布,翻译遵循 CC BY-SA 4.0 许可协议

我试图理解 super()

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

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

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

在 Python 2 中,我们需要调用 super 像这样使用定义类的名称和 self ,但从现在开始我们将避免这种情况,因为它是多余的,速度较慢(由于名称查找),以及更冗长的内容(如果您还没有更新 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 中获得了一个间接层,它用于定义类-确定下一个类的 __init__ 在 MRO 中查找。

我在 规范问题 How to use ‘super’ in 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)
                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 对象,我们将不得不在任何地方编写此手动代码(或重新创建它!)以确保我们在方法解析顺序中调用正确的下一个方法!

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 使用超级, 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'

幸运的是,Python 3 的新 super() 不带参数的调用方法允许我们回避这个问题。

原文由 Russia Must Remove Putin 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题