我试图理解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__()
此代码的主要区别在于,您在with中ChildB
获得了一个间接层,它使用定义它的类来确定要在 MRO 中查找的下一个类。__init__
super
__init__
我在规范问题的答案中说明了这种差异,如何在 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
使用 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__
in 代替类名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()
不带参数的新调用方法使我们能够回避这个问题。