通过。了解Python super()
这个问题在这里已经有了答案:
super()
可以避免显式引用基类,这很好。 但主要优点是多重继承,可以发生各种有趣的事情。 如果你还没有,请参阅super的标准文档。
请注意,在Python 3.0中更改了语法:您可以只说super().__init__()
而不是super(ChildB, self).__init__()
哪个IMO更好。
我想了解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中查找。
我在标准问题(如何在Python中使用'super')中说明了这种差异,它演示了依赖注入和协作多重继承 。
如果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__
,使调用函数成为类的闭包)以及该函数的第一个参数,该参数应该是通知它的实例或类使用哪种方法解析顺序(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
,所以ChildB
>>> 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__, 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 "<stdin>", line 1, in <module>
File "<stdin>", 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
用mSquare = Square('a', 10,10)
,Python的要求构造Rectangle
,因为我还没有给Square
自己的构造。 但是,在Rectangle
的构造函数中, super(self.__class__,self)
将返回mSquare
的超类,因此它再次调用Rectangle
的构造函数。 正如@S_C所提到的,这就是无限循环的发生。 在这种情况下,当我运行super(...).__init__()
我调用Rectangle
的构造函数,但是因为我没有给它任何参数,所以我会得到一个错误。