我试图理解super()
我们使用的原因是,可能使用协同多重继承的子类将在方法解析顺序 (MRO) 中调用正确的下一个父类函数。super
在Python 3中,我们可以这样称呼它:
class ChildB(Base):
def __init__(self):
super().__init__()
在 Python 2 中,我们被要求像这样使用定义类的名称和 调用,但从现在开始我们将避免这种情况,因为它是冗余的,较慢的(由于名称查找),并且更详细(因此,如果您还没有更新Python!):super
self
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__()
现在记住,使用超级,不: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__()
并且不调用 UserDependency 方法:UserA
>>> UserA()
UserA init'ed
ChildA init'ed
Base init'ed
<__main__.UserA object at 0x0000000003403BA8>
但实际上确实调用了 UserDependency,因为 调用 :UserB
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次的反对票,但回答者坚持建议它,直到一位善良的编辑解决了他的问题。
说明: 用作 中类名的替代品将导致递归。 让我们在 MRO 中查找子类的下一个父类(请参阅本答案的第一部分)。如果你告诉我们在子实例的方法中,它将在行中查找下一个方法(可能是这个),导致递归,这可能会导致逻辑故障(在回答者的例子中,它确实如此)或超过递归深度。self.__class__
super()
super
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()