我是Python的初学者,并且使用Lutz的书来理解Python的OOPS。这个问题可能是基本的,但我将不胜感激。我研究了SO,找到了“如何”的答案,但没有找到“为什么”的答案。
据我从书中了解,如果Sub
继承了Super
,则不必调用超类(Super
的)__init__()
方法。
示例:
class Super:
def __init__(self,name):
self.name=name
print("Name is:",name)
class Sub(Super):
pass
a = Sub("Harry")
a.name
以上代码确实将属性name
分配给对象a
。它还按预期打印name
。
但是,如果我将代码修改为:
class Super:
def __init__(self,name):
print("Inside Super __init__")
self.name=name
print("Name is:",name)
class Sub(Super):
def __init__(self,name):
Super(name) #Call __init__ directly
a = Sub("Harry")
a.name
上面的代码无法正常工作。好的,我的意思是,尽管确实Super.__init__()
被调用(从打印语句中看到),但a
没有附加属性。运行a.name
时出现错误,AttributeError: 'Sub' object has no attribute 'name'
我在SO上研究了该主题,并找到了Chain-calling parent constructors in python和Why aren't superclass __init__ methods automatically invoked?
上的修复程序。这两个线程讨论如何修复它,但没有提供原因。
问题:为什么,我需要使用Super
或__init__
而不是直接致电{{1 }}?
在Super.__init__(self, name)
和super(Sub, self).__init__(name)
中,我们看到Super的Super(name)
被调用(从打印语句中看到),但是仅在Super.__init__(self, name)
中,我们看到该属性被获取附加到Super(name)
类。
__init__()
不会自动将Super.__init__(self, name)
(子对象)传递给Sub
吗?现在,您可能会问我如何知道Super(name)
是自动传递的?如果我将self
修改为Super
,则会收到一条错误消息self
。从书中可以了解到,Super(name)
是自动传递的。因此,有效地,我们最终两次通过了Super(self,name)
。
即使运行TypeError: __init__() takes 2 positional arguments but 3 were given
,我也不知道为什么self
不将self
属性附加到Super(name)
上。我将不胜感激。
作为参考,这是根据我对SO的研究得出的代码的工作版本:
name
PS:我正在Anaconda发行版下使用Sub
。
答案 0 :(得分:2)
据我从书中了解到,如果Sub继承了Super,则不必调用超类(
Super
的)__init__()
方法。
这具有误导性。的确,您不需要调用来调用超类的__init__
方法,但是如果您不这样做,则__init__
中所做的任何事情都不会发生。对于普通班级,所有这些都需要完成。 偶尔很有用,通常是在未将类设计为从其继承时使用的,例如:
class Rot13Reader:
def __init__(self, filename):
self.file = open(filename):
def close(self):
self.file.close()
def dostuff(self):
line = next(file)
return codecs.encode(line, 'rot13')
想象一下,您需要此类的所有行为,但要使用字符串而不是文件。唯一的方法是跳过open
:
class LocalRot13Reader(Rot13Reader):
def __init__(self, s):
# don't call super().__init__, because we don't have a filename to open
# instead, set up self.file with something else
self.file = io.StringIO(s)
在这里,我们要避免超类中的self.file
分配。对于您的情况-与几乎所有您将要编写的类一样-您不要要避免在超类中使用self.name
赋值。这就是为什么即使Python 允许您不调用超类的__init__
,您也几乎总是调用它。
请注意,这里__init__
没什么特别的。例如,我们可以重写dostuff
来调用基类的版本,然后执行其他操作:
def dostuff(self):
result = super().dostuff()
return result.upper()
…或者我们可以覆盖close
并有意不调用基类:
def close(self):
# do nothing, including no super, because we borrowed our file
唯一的区别是,避免调用基类的良好原因在普通方法中比在__init__
中更为常见。
问题:为什么我需要使用
Super's __init__
或Super.__init__(self, name)
而不是直接调用super(Sub, self).__init__(name)
来呼叫Super(name)
?
因为这些功能有很大不同。
Super(name)
构造一个新的Super
实例,在其上调用__init__(name)
并将其返回给您。然后您忽略该值。
特别是,Super.__init__
确实会被调用一次,但是调用它的self
是新的Super
实例,您只是要扔掉它,在Super(name)
情况下,而在self
情况下则是您自己的super(Sub, self).__init__(name)
。
因此,在第一种情况下,它在其他被丢弃的对象上设置了name
属性,却没有人在您的对象上设置它,这就是self.name
之后会引发{{ 1}}。
如果您在两个类的AttributeError
方法中都添加了一些内容,以显示所涉及的实例,则可能有助于您理解这一点:
__init__
如果最后一行是class Super:
def __init__(self,name):
print(f"Inside Super __init__ for {self}")
self.name=name
print("Name is:",name)
class Sub(Super):
def __init__(self,name):
print(f"Inside Sub __init__ for {self}")
# line you want to experiment with goes here.
,super().__init__(name)
或super(Sub, self).__init__name)
,您将看到以下内容:
Super.__init__(self, name)
请注意,在两种情况下,它都是同一个对象,即Inside Sub __init__ for <__main__.Sub object at 0x10f7a9e80>
Inside Super __init__ for <__main__.Sub object at 0x10f7a9e80>
在地址0x10f7a9e80上。
…但是如果最后一行是Sub
:
Super(name)
现在我们有两个不同的对象,它们的地址分别为0x10f7a9ea0和0x10f7a9ec0,并且类型不同。
如果您对幕后的魔力感到好奇,Inside Sub __init__ for <__main__.Sub object at 0x10f7a9ea0>
Inside Super __init__ for <__main__.Super object at 0x10f7a9ec0>
会做类似的事情(简化了一点,并跳过了一些步骤 1 ):
Super(name)
…而_newobj = Super.__new__(Super)
if isinstance(_newobj, Super):
Super.__init__(_newobj, name)
则是这样的:
super(Sub, self).__init__(name)
请注意,如果一本书告诉您使用_basecls = magically_find_next_class_in_mro(Sub)
_basecls.__init__(self, name)
或super(Sub, self).__init__(name)
,则可能是为Python 2编写的过时的书。
在Python 3中,您只需执行以下操作:
Super.__init__(self, name)
:按方法解析顺序调用正确的下一个超类。您几乎总是想要这个。super().__init__(name)
:调用正确的下一个超类-除非您犯了一个错误并在那里弄错了super(Sub, self).__init__(name)
。仅当您要编写必须在2.7和3.x中运行的双版本代码时,才需要使用此代码。Sub
:无论是否为正确的下一个超类,都调用Super.__init__(self, name)
。仅在方法解析顺序错误并且需要解决时才需要此方法。 2 如果您想了解更多,这一切都在文档中,但这可能有点令人生畏:
original introduction to super
, __new__
, and all the related features对我的理解非常有帮助。我不确定这是否会对那些已经不了解老式Python类的人有帮助,但是它写得不错,Guido(显然)知道他在说什么,所以值得一读
1。这种解释中最大的弊端是Super
实际上返回了一个代理对象,该代理对象的行为类似于绑定到super
的{{1}},绑定方法的方式相同,可用于绑定方法,例如_baseclass
。如果您知道方法的工作原理,那么这是很有用/有趣的知识,但如果您不知道方法的话,可能只会造成额外的困惑。
2。 …或者如果您正在使用不支持self
(或正确的方法解析顺序)的老式类。没有老式类的Python 3永远不会出现这种情况。但是,不幸的是,您会在很多tkinter示例中看到它,因为最好的教程仍然是Effbot,它是为Python 2.3编写的,当时Tkinter都是老式类,并且从未进行过更新。 >
答案 1 :(得分:1)
Super(name)
不是对超类__init__
的“直接调用”。毕竟,您打电话给Super
,而不是Super.__init__
。
Super.__init__
接受未初始化的Super
实例并将其初始化。 Super
创建并初始化一个与您要初始化的实例完全独立的新实例(然后立即将新实例扔掉)。您要初始化的实例未更改。
答案 2 :(得分:1)
Super(name)
实例化super的新实例。想想这个例子:
def __init__(self, name):
x1 = Super(name)
x2 = Super("some other name")
assert x1 is not self
assert x2 is not self
为了在当前实例上显式调用Super
的构造函数,您必须使用以下语法:
def __init__(self, name):
Super.__init__(self, name)
现在,如果您是初学者,也许您不想进一步阅读。
如果这样做,将会发现有充分的理由使用super(Sub, self).__init__(name)
(或Python 3中的super().__init__(name)
)而不是Super.__init__(self, name)
。
Super.__init__(self, name)
实际上是您的超类, Super
就可以正常工作。但实际上,您永远都不知道。
您可能具有以下代码:
class Super:
def __init__(self):
print('Super __init__')
class Sub(Super):
def __init__(self):
print('Sub __init__')
Super.__init__(self)
class Sub2(Super):
def __init__(self):
print('Sub2 __init__')
Super.__init__(self)
class SubSub(Sub, Sub2):
pass
您现在希望SubSub()
最终会调用上述所有构造函数,但不会:
>>> x = SubSub()
Sub __init__
Super __init__
>>>
要更正它,您必须这样做:
class Super:
def __init__(self):
print('Super __init__')
class Sub(Super):
def __init__(self):
print('Sub __init__')
super().__init__()
class Sub2(Super):
def __init__(self):
print('Sub2 __init__')
super().__init__()
class SubSub(Sub, Sub2):
pass
现在可以使用了
>>> x = SubSub()
Sub __init__
Sub2 __init__
Super __init__
>>>
原因是Sub
的超类被声明为Super
,如果在类SubSub
中有多个继承,Python的MRO将继承设置为:{{1} }从SubSub
继承,而Sub
从Sub2
继承,Super
从object
继承。
您可以测试:
>>> SubSub.__mro__
(<class '__main__.SubSub'>, <class '__main__.Sub'>, <class '__main__.Sub2'>, <class '__main__.Super'>, <class 'object'>)
现在,每个类的构造函数中的super()
调用会在MRO中找到下一个类,以便可以调用该类的构造函数。