我正在尝试理解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)
我下面进一步解释。
“这段代码有什么不同之处呢?”
class ChildA(Base):
def __init__(self):
Base.__init__(self)
class ChildB(Base):
def __init__(self):
super().__init__()
在这段代码中,与
ChildA
相比,
ChildB
中的
__init__
使用了
super
进行了一层间接引用,使用定义它的类来确定下一个类在 MRO 中要查找的
__init__
。
我在
经典问题 "如何在 Python 中使用 'super'" 的答案中举例说明了这个区别,演示了
依赖注入与
协同多重继承。
如果 Python 没有 super
以下是实际上与
super
(C 语言中的实现方式,减去一些检查和回退行为,并转换为 Python)密切相关的代码:
class ChildB(Base):
def __init__(self):
mro = type(self).mro()
check_next = mro.index(ChildB) + 1
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:]:
if hasattr(next_class, '__init__'):
next_class.__init__(self)
break
如果没有
super
对象,我们就必须在每个地方手动编写这些代码(或重新创建它们!)以确保我们调用方法解析顺序中的正确下一个方法!那么Python 3中的
super
是如何做到这一点的呢?它获取调用堆栈帧,并查找类(隐式存储为局部自由变量
__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个踩,回答者仍坚持建议,直到一个友好的编辑解决了他的问题。)
解释:在super()
中使用self.__class__
替代类名会导致递归。 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()
调用方法不需要参数,幸运的是,这使我们能够回避这个问题。
Event(tuple)
,它创建元组(时间戳,描述),其中时间戳应默认为当前时间。因此,类似于e = Event(description="stored the current time")
这样的东西应该给出一个元组的子类Event
的实例(1653520485,"stored...")
。但在__init __()
中,我不能像对字典的子类那样修改self
。所以我想我可以使用super().__init__
来设置元组self
的组件。我可以吗? - Max