我正在尝试了解super()
的用法。从它的外观来看,可以创建两个子类,就好了。
我很想知道以下两个子课程之间的实际差异。
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()
答案 0 :(得分:1615)
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(),这是非常明确的解释。
答案 1 :(得分:573)
我试图了解
super()
我们使用super
的原因是,可能使用协作多重继承的子类将在方法解析顺序(MRO)中调用正确的下一个父类函数。
在Python 3中,我们可以这样称呼它:
class ChildB(Base):
def __init__(self):
super().__init__()
在Python 2中,我们需要像这样使用它:
super(ChildB, self).__init__()
如果没有超级,你在使用多重继承的能力上受到限制:
Base.__init__(self) # Avoid this.
我在下面进一步解释。
"这段代码实际上有什么区别?:"
class ChildA(Base):
def __init__(self):
Base.__init__(self)
class ChildB(Base):
def __init__(self):
super(ChildB, self).__init__()
# super().__init__() # you can call super like this in Python 3!
此代码的主要区别在于,您在__init__
中使用super
获得了一个间接层,该层使用当前类来确定下一个类__init__
查阅MRO。
我在canonical question, How to use 'super' in Python?的答案中说明了这种差异,它展示了依赖注入和合作多重继承。
super
此处的代码实际上与super
完全相同(如何在C中实现,减去一些检查和回退行为,并转换为Python):
class ChildB(Base):
def __init__(self):
mro = type(self).mro() # Get the Method Resolution Order.
check_next = mro.index(ChildB) + 1 # Start looking 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__
,使调用函数成为类的闭包)和该函数的第一个参数,应该是通知它使用哪个Method Resolution Order(MRO)的实例或类。
因为它需要MRO的第一个参数using super
with static methods is impossible。
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(ChildB, self).__init__()
让我们创建一个我们希望在Child之后调用的依赖项:
class UserDependency(Base):
def __init__(self):
print("UserDependency init'ed")
super(UserDependency, self).__init__()
现在请记住,ChildB
使用超级,ChildA
不使用:
class UserA(ChildA, UserDependency):
def __init__(self):
print("UserA init'ed")
super(UserA, self).__init__()
class UserB(ChildB, UserDependency):
def __init__(self):
print("UserB init'ed")
super(UserB, self).__init__()
并且UserA
不会调用UserDependency方法:
>>> UserA()
UserA init'ed
ChildA init'ed
Base init'ed
<__main__.UserA object at 0x0000000003403BA8>
但是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个downvotes,回答者坚持提出建议,直到一位善良的编辑解决了他的问题。) < / p>
说明:那个回答建议像这样调用超级:
super(self.__class__, self).__init__()
这完全错误。 super
让我们在子类中查找MRO中的下一个父级(请参阅本答案的第一部分)。如果你告诉super
我们在子实例的方法中,那么它将在行中查找下一个方法(可能是这个)导致递归,可能导致逻辑失败(在回答者& #39;例如,它确实)或超过递归深度的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'
答案 2 :(得分:240)
已经注意到在Python 3.0+中你可以使用
super().__init__()
进行调用,这是简洁的,不需要您明确引用父OR类名称,这可能很方便。我只想在Python 2.7或更低版本中添加它,可以通过编写self.__class__
而不是类名来获得这种对名称不敏感的行为,即
super(self.__class__, self).__init__()
但是,对于从您的类继承的任何类,这会中断对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
创建mSquare = Square('a', 10,10)
时,Python调用Rectangle
的构造函数,因为我没有给Square
自己的构造函数。但是,在Rectangle
的构造函数中,调用super(self.__class__,self)
将返回mSquare
的超类,因此它再次调用Rectangle
的构造函数。这就是无限循环的发生方式,正如@S_C所提到的那样。在这种情况下,当我运行super(...).__init__()
时,我正在调用Rectangle
的构造函数,但由于我没有给它任何参数,我将收到错误。
答案 3 :(得分:80)
超级没有副作用
Base = ChildB
Base()
按预期工作
Base = ChildA
Base()
进入无限递归。
答案 4 :(得分:71)
只是提前......使用Python 2.7,我相信自从版本2.2中引入了super()
以来,如果其中一个父级继承自最终的类,则只能调用super()
继承object
(new-style classes)。
就个人而言,对于python 2.7代码,我将继续使用BaseClassName.__init__(self, args)
,直到我真正获得使用super()
的优势。
答案 5 :(得分:50)
真的没有。 super()
查看MRO中的下一个类(方法解析顺序,使用cls.__mro__
访问)来调用方法。只需调用基座__init__
即可调用基座__init__
。碰巧的是,MRO只有一个项目 - 基础。所以你真的做了同样的事情,但用super()
更好的方式(特别是如果你以后进入多重继承)。
答案 6 :(得分:28)
主要区别在于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 the Method Resolution Order is ChildC->ChildB->Mixin->Base
然后 Base
不再是ChildB
实例的ChildC
的父级。如果super(ChildB, self)
是Mixin
个实例,则self
会指向ChildC
。
您已在Mixin
和ChildB
之间插入了Base
。你可以利用super()
因此,如果您设计了类以便可以在协作多重继承方案中使用它们,则使用super
,因为您实际上并不知道谁将成为运行时的祖先。
super considered super post和pycon 2015 accompanying video很好地解释了这一点。