理解 Python super() 与 __init__() 方法 [重复]
- 2024-11-20 08:44:00
- admin 原创
- 7
问题描述:
为何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()
解决方案 1:
super()
让您避免显式引用基类,这很好。但主要优势在于多重继承,其中可以发生各种有趣的事情。如果您还没有看过super 的标准文档,请参阅。
请注意,Python 3.0 中的语法发生了变化:您可以直接使用 whichsuper().__init__()
来代替,在super(ChildB, self).__init__()
我看来,这要好得多。标准文档还引用了使用super()
which 的指南,这很有说明性。
解决方案 2:
我试图理解
super()
我们这样做的原因super
是,可能使用协作多重继承的子类将按照方法解析顺序(MRO)调用正确的下一个父类函数。
在Python3中我们可以这样调用:
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__()
此代码的主要区别在于,在withChildB
中获得了一个间接层,它使用它定义的类来确定下一个要在 MRO 中查找的类。__init__
`super`__init__
我在经典问题“如何在 Python 中使用‘super’?”的回答中说明了这种差异,该回答演示了依赖注入和协作多重继承。
如果 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
对象,我们就必须在各处编写这个手动代码(或重新创建它!)以确保我们按照方法解析顺序调用正确的下一个方法!
在 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 个反对票,回答者仍然坚持提出该答案,直到一位好心的编辑解决了他的问题。)
解释:使用self.__class__
代替显式传递类名将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()
无参数调用方法让我们避开了这个问题。
解决方案 3:
值得注意的是,在 Python 3.0+ 中,你可以使用
super().__init__()
进行调用,这很简洁,不需要您明确引用父类或类名,这很方便。我只想补充一点,对于 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,以便我可以重新实现其他方法。
当我创建一个Square
using时mSquare = Square('a', 10,10)
,Python 会调用 的构造函数,Rectangle
因为我没有给出Square
它自己的构造函数。但是,在 的构造函数中Rectangle
,调用super(self.__class__,self)
将返回 的超类mSquare
,因此它会再次调用 的构造函数Rectangle
。这就是无限循环发生的方式,正如@S_C 所提到的。在这种情况下,当我运行时,super(...).__init__()
我正在调用 的构造函数,Rectangle
但由于我没有给它任何参数,所以我会收到错误。
解决方案 4:
Super 没有副作用
Base = ChildB
Base()
按预期工作
Base = ChildA
Base()
陷入无限递归。
解决方案 5:
只是提醒一下...使用 Python 2.7,并且我相信自从super()
2.2 版本引入以来,只有super()
当其中一个父类从最终继承的类object
(新式类)继承时,您才能调用。
就我个人而言,对于 python 2.7 代码,我将继续使用,BaseClassName.__init__(self, args)
直到我真正获得使用的优势super()
。
解决方案 6:
实际上没有。super()
查看 MRO(方法解析顺序,使用 访问cls.__mro__
)中的下一个类来调用方法。只需调用基类__init__
就会调用基类__init__
。实际上,MRO 只有一个项目——基类。因此,您实际上是在做完全相同的事情,但使用 的方式更好super()
(特别是如果您以后进入多重继承)。
解决方案 7:
主要区别在于ChildA.__init__
将无条件调用Base.__init__
而ChildB.__init__
将调用祖先线__init__
中恰好是ChildB
祖先self
的任何类
(这可能与您的预期不同)。
如果添加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
thenBase
不再是ChildB
forChildC
实例的父级。现在super(ChildB, self)
将指向Mixin
ifself
是一个ChildC
实例。
Mixin
您已在ChildB
和之间插入Base
。您可以利用它super()
因此,如果您设计类以便它们可以在协作多重继承场景中使用,那么您可以使用它,super
因为您实际上并不知道谁将成为运行时的祖先。
这篇经过深思熟虑的超级帖子和pycon 2015 附带视频很好地解释了这一点。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件