ChatGPT解决这个技术问题 Extra ChatGPT

使用 __init__() 方法理解 Python super() [重复]

这个问题在这里已经有了答案:“super”在 Python 中做了什么? - super().__init__() 和显式超类 __init__() 之间的区别 (11 个答案) 6 年前关闭。

为什么使用 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()
这是对值得一读的课程的非常简单的介绍:realpython.com/python-super/…。它比我们大多数人给出的答案更容易消化,我认为在 python 的实现中过于详细。它还提供了具体示例。
我还是不明白。我想定义一个 class Event(tuple) 来创建元组(时间戳、描述)以及时间戳应默认为当前时间的位置。因此,像 e = Event(description="stored the current time") 这样的东西应该给出元组 (1653520485,"stored...") 的子类 Event 的实例。但是在 __init__() 中,我不能像修改 dict 的子类那样修改 self。所以我想我可以使用 super().__init__ 来设置元组 self 的组件。我可以吗 ?

N
Neuron

super() 让您避免显式引用基类,这很好。但主要优势来自于多重继承,其中各种 fun stuff 都可能发生。如果您还没有,请参阅 standard docs on super

请注意 the syntax changed in Python 3.0:您可以只说 super().__init__() 而不是 super(ChildB, self).__init__(),IMO 更好一些。标准文档还引用了一个非常具有解释性的 guide to using super()


@rimiro super() 的语法是 super([type [, object]]) 这将返回 type 的超类。所以在这种情况下,ChildB 的超类将被返回。如果省略第二个参数,则返回的超级对象是未绑定的。如果第二个参数是一个对象,那么 isinstance(object, type) 必须为真。
如果您在这里仍然感到困惑,请阅读 Aaron Hall 的回答,您会更开心地离开此页面:stackoverflow.com/a/27134600/1886357
你能解释一下代码的作用吗?我不想再点击 100 万个地方来找到这个问题的答案。
R
Russia Must Remove Putin

我试图理解 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 获得了一个间接层,它使用定义它的类来确定下一个类的 __init__ 以在维修保养

我在 canonical question, 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 的第一个参数,所以 using super with static methods is impossible as they do not have access to the MRO of the class from which they are called

对其他答案的批评:

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 我们在子实例的方法中,那么它将查找行中的下一个方法(可能是这个)导致递归,可能导致逻辑错误(在回答者的示例中,确实如此)或 { 5}当超出递归深度时。

>>> 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() 调用方法允许我们回避这个问题。


我仍然需要围绕这个 super() 函数努力,但是,就深度和细节而言,这个答案显然是最好的。我也非常感谢答案中的批评。它还有助于通过识别其他答案中的陷阱来更好地理解这个概念。谢谢 !
我一直在使用 tk.Tk.__init__(self) 而不是 super().__init__(),因为我不完全理解 super 是什么,但这篇文章非常有启发性。我想在 Tkinter 类 tk.Tk.__init__(self)super().__init__() 的情况下是同一件事,但看起来你是说我们应该避免做类似 Base.__init__(self) 的事情所以我可能会切换到 super() 即使我仍在尝试掌握它的复杂性。
这个答案特别全面,确实填补了我的知识空白。向您致敬,先生。
您的第二个“本机 Python”示例的行为与第一个示例不同(并且作为基于 super 的解决方案)。使用的 hasattr(next_class, '__init__') 可能暗示存在不必要的方法。例如,想象一下典型的菱形层次结构 class A(): ...; class B(A): ...; class C(A): ...; class D(B, C): ...,其中 AС 有自己的 __init__ 方法,而 D__init__,例如在所描述的示例中。然后调用 D().__init__() 将导致调用 A.__init__ 而不是 C.__init__
也许 if '__init__' in next_class.__dict__: 更好
A
AnjoMan

已经注意到,在 Python 3.0+ 中,您可以使用

super().__init__()

进行调用,这很简洁,不需要您显式引用父 OR 类名称,这很方便。我只想为 Python 2.7 或更低版本添加它,有些人通过编写 self.__class__ 而不是类名来实现对名称不敏感的行为,即

super(self.__class__, self).__init__()  # DON'T DO THIS!

但是,这会中断对从您的类继承的任何类的 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 的构造函数,但由于我没有给它任何参数,我会得到一个错误。


这个答案表明,如果您在不提供新 __init__ 的情况下再次子类化,super(self.__class__, self).__init__() 将不起作用。然后你有一个无限递归。
这个答案很荒谬。如果您打算以这种方式滥用 super,您不妨硬编码基类名称。它的错误比这要少。 super 的第一个论点的全部意义在于它不一定是 self 的类型。请阅读 rhettinger 的“super被认为是超级”(或观看他的一些视频)。
此处为 Python 2 演示的快捷方式存在已经提到的缺陷。不要使用它,否则您的代码将以您无法预测的方式中断。这个“方便的捷径”打破了超级,但你可能直到你花了很多时间调试才意识到它。如果 super 过于冗长,请使用 Python 3。
@Tino 我不太同意您的编辑。说一个人不能做某事和一个人不应该做这两者是没有意义的 - 可以做我在帖子中描述的事情,出于我列出的原因,这是一个坏主意。
告诉某人他们可以做一些被证明是不正确的事情是没有意义的。您可以将 echo 别名为 python。没有人会建议它!
A
Art

超级没有副作用

Base = ChildB

Base()

按预期工作

Base = ChildA

Base()

进入无限递归。


“超级没有副作用”的说法在这种情况下没有意义。 Super 只是保证我们按照方法解析顺序调用正确的下一个类的方法,而另一种方式硬编码要调用的下一个方法,这使得协作多重继承更加困难。
这个答案是零碎的(代码示例仅作为答案中代码的延续才有意义。)
r
rgenito

请注意...对于 Python 2.7,我相信自从在 2.2 版中引入 super() 以来,如果其中一个父级继承自最终继承 object 的类({2 })。

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


很好的一点。如果你没有明确提到:class Base(object): 那么你会得到这样的错误:“TypeError: must be type, not classobj”
D
Devin Jeanpierre

没有,真的。 super() 查看 MRO 中的下一个类(方法解析顺序,使用 cls.__mro__ 访问)以调用方法。只需调用基础 __init__ 即可调用基础 __init__。碰巧的是,MRO 只有一个项目——基础。因此,您确实在做完全相同的事情,但使用 super() 以更好的方式(特别是如果您稍后进入多重继承)。


我懂了。您能否详细说明为什么使用具有多重继承的 super() 更好?对我来说, base.__init__(self) 更短(更干净)。如果我有两个基类,那将是其中的两条,或者两条 super() 行。还是我误解了您所说的“更好”是什么意思?
实际上,这将是一个 super() 行。当你有多重继承时,MRO 仍然是平坦的。所以第一个 super().__init__ 调用调用下一个类的 init,然后调用下一个,依此类推。你真的应该查看一些关于它的文档。
子类 MRO 也包含对象 - 类的 MRO 在 mro 类变量中可见。
另请注意,经典类(2.2 之前)不支持 super - 您必须明确引用基类。
“子类 MRO 也包含对象 - 类的 MRO 在 mro 类变量中可见。”这是一个很大的哎呀。哎呀。
H
HoldOffHunger

主要区别在于 ChildA.__init__ 将无条件调用 Base.__init__ChildB.__init__ 将调用 __init__任何类恰好是 self 的祖先行中的 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 不再是 ChildC 实例的 ChildB 的父级。如果 selfChildC 实例,现在 super(ChildB, self) 将指向 Mixin

您在 ChildBBase 之间插入了 Mixin。您可以通过 super() 来利用它

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

super considered super postpycon 2015 accompanying video 很好地解释了这一点。


这个。 super(ChildB, self) 的含义根据 self 引用的对象的 MRO 而变化,直到运行时才能知道。换句话说,ChildB 的作者无法知道在所有情况下 super() 将解析为什么,除非他们可以保证 ChildB 永远不会被子类化。