这个问题在这里已经有了答案:“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()
class Event(tuple)
来创建元组(时间戳、描述)以及时间戳应默认为当前时间的位置。因此,像 e = Event(description="stored the current time")
这样的东西应该给出元组 (1653520485,"stored...")
的子类 Event
的实例。但是在 __init__()
中,我不能像修改 dict
的子类那样修改 self
。所以我想我可以使用 super().__init__
来设置元组 self
的组件。我可以吗 ?
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()
。
我试图理解 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()
即使我仍在尝试掌握它的复杂性。
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__:
更好
已经注意到,在 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__()
将不起作用。然后你有一个无限递归。
echo
别名为 python
。没有人会建议它!
超级没有副作用
Base = ChildB
Base()
按预期工作
Base = ChildA
Base()
进入无限递归。
请注意...对于 Python 2.7,我相信自从在 2.2 版中引入 super()
以来,如果其中一个父级继承自最终继承 object
的类({2 })。
就我个人而言,对于 python 2.7 代码,我将继续使用 BaseClassName.__init__(self, args)
,直到我真正获得使用 super()
的优势。
没有,真的。 super()
查看 MRO 中的下一个类(方法解析顺序,使用 cls.__mro__
访问)以调用方法。只需调用基础 __init__
即可调用基础 __init__
。碰巧的是,MRO 只有一个项目——基础。因此,您确实在做完全相同的事情,但使用 super()
以更好的方式(特别是如果您稍后进入多重继承)。
主要区别在于 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
的父级。如果 self
是 ChildC
实例,现在 super(ChildB, self)
将指向 Mixin
。
您在 ChildB
和 Base
之间插入了 Mixin
。您可以通过 super()
来利用它
因此,如果您将类设计为可以在协作多重继承方案中使用,则使用 super
因为您并不真正知道谁将成为运行时的祖先。
super considered super post 和 pycon 2015 accompanying video 很好地解释了这一点。
super(ChildB, self)
的含义根据 self
引用的对象的 MRO 而变化,直到运行时才能知道。换句话说,ChildB
的作者无法知道在所有情况下 super()
将解析为什么,除非他们可以保证 ChildB
永远不会被子类化。
不定期副业成功案例分享
super([type [, object]])
这将返回type
的超类。所以在这种情况下,ChildB
的超类将被返回。如果省略第二个参数,则返回的超级对象是未绑定的。如果第二个参数是一个对象,那么isinstance(object, type)
必须为真。