我正在努力了解它的用法super()
.从它的外观来看,可以创建两个子类,就好了.
我很想知道以下2个孩子班级之间的实际差异.
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()
Kiv.. 1725
super()
让你避免显式引用基类,这可能很好.但主要优势在于多重继承,可以发生各种有趣的事情.如果您还没有,请参阅super上的标准文档.
请注意,Python 3.0中的语法已更改:您可以说,super().__init__()
而不是super(ChildB, self).__init__()
哪个IMO更好一点.标准文档也引用了使用super()的指南,这是非常明确的解释.
super()
让你避免显式引用基类,这可能很好.但主要优势在于多重继承,可以发生各种有趣的事情.如果您还没有,请参阅super上的标准文档.
请注意,Python 3.0中的语法已更改:您可以说,super().__init__()
而不是super(ChildB, self).__init__()
哪个IMO更好一点.标准文档也引用了使用super()的指南,这是非常明确的解释.
我想了解一下
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__
with中获得了一个间接层super
,它使用当前类来确定__init__
要在MRO中查找的下一个类.
我在规范问题的答案中说明了这种差异,如何在Python中使用'super'?,它演示了依赖注入和协作多重继承.
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
对象,我们必须在任何地方编写此手动代码(或重新创建它!)以确保我们在方法解析顺序中调用正确的下一个方法!
超级如何在没有明确告知调用方法的哪个类和实例的情况下在Python 3中执行此操作?
它获取调用堆栈帧,并找到类(隐式存储为本地自由变量,__class__
使调用函数成为类的闭包)和该函数的第一个参数,该参数应该是通知它的实例或类要使用的方法解决顺序(MRO).
因为它需要MRO的第一个参数,所以使用super
静态方法是不可能的.
使用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,回答者坚持提出建议,直到一位善良的编辑解决了他的问题.)
说明:那个回答建议像这样调用super:
super(self.__class__, self).__init__()
这是完全错误的.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 "", line 1, in File " ", line 3, in __init__ TypeError: __init__() missing 2 required positional arguments: 'width' and 'height'
已经注意到,在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
using时mSquare = Square('a', 10,10)
,Python会调用构造函数,Rectangle
因为我没有给出Square
自己的构造函数.但是,在构造函数中Rectangle
,调用super(self.__class__,self)
将返回超类mSquare
,因此它Rectangle
再次调用构造函数.这就是无限循环的发生方式,正如@S_C所提到的那样.在这种情况下,当我运行时,super(...).__init__()
我正在调用构造函数,Rectangle
但由于我没有给它任何参数,我将得到一个错误.
超级没有副作用
Base = ChildB Base()
按预期工作
Base = ChildA Base()
进入无限递归.
使用Python 2.7,我相信自从super()
2.2版本引入以来,只有super()
其中一个父级继承自最终继承的类object
(新式类)时才能调用.
就个人而言,至于python 2.7代码,我将继续使用,BaseClassName.__init__(self, args)
直到我真正获得使用的优势super()
.
真的没有.super()
查看MRO中的下一个类(方法解析顺序,访问方式cls.__mro__
)以调用方法.只是调用基地__init__
调用基地__init__
.碰巧的是,MRO只有一个项目 - 基础.所以你真的做了完全相同的事情,但以更好的方式super()
(特别是如果你以后进入多重继承).
主要的区别是,ChildA.__init__
将无条件调用Base.__init__
,而ChildB.__init__
将调用__init__
在任何类恰好是ChildB
祖先self
的祖先的的路线
(可能与预期的不同).
如果添加ClassC
使用多重继承的a:
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
if self
是一个ChildC
实例.
已经插入Mixin
之间ChildB
和Base
.你可以利用它super()
因此,如果您设计了类以便可以在协作多重继承方案中使用它们,那么您可以使用它,super
因为您实际上并不知道谁将成为运行时的祖先.
该超认为超级岗位和PYCON 2015年影随行解释这相当不错.