问题:为什么不自动调用超类__init__方法?
为什么Python设计人员会决定子类的__init__()
方法不会__init__()
像某些其他语言那样自动调用其超类的方法?Pythonic和推荐的习语真的像下面这样吗?
class Superclass(object):
def __init__(self):
print 'Do something'
class Subclass(Superclass):
def __init__(self):
super(Subclass, self).__init__()
print 'Do something else'
回答 0
Python __init__
与其他语言的构造函数之间的关键区别在于,__init__
它不是构造函数:它是一个初始化程序(实际的构造函数(如果有,但是请参阅下文;-)是__new__
并且再次完全不同。虽然构建所有超(,毫无疑问,这样做,你继续向下构建“之前”)显然是说你的一部分构建一个子类的实例,这显然是不适合的情况下初始化,因为在许多用例中,超类的初始化需要被跳过,更改和控制-发生在子类初始化的“中间”,如果发生的话,等等。
基本上,出于完全相同的原因,初始化程序的超类委派在Python中不是自动的,此类委派对于任何其他方法也不是自动的-请注意,那些“其他语言” 对任何其他方法都不会自动进行超类委派其他方法… 只是针对构造函数(如果适用,也应包含析构函数),正如我提到的,这不是 Python的__init__
方法。(的行为__new__
也很特殊,尽管实际上与您的问题没有直接关系,因为它__new__
是一个奇特的构造函数,实际上并不一定要构造任何东西-可以很好地返回一个现有实例,甚至一个非实例…显然Python为您提供了很多比您要记住的“其他语言”,对机械的控制更多,它本身也没有自动委派__new__
!-)。
回答 1
当人们模仿“ Python禅”时,我有些尴尬,好像这是任何事情的正当理由。这是一种设计理念;特定的设计决策总是可以用更具体的术语来解释-必须如此,否则“ Zen of Python”将成为做任何事情的借口。
原因很简单:您不必以与构造基类的方式完全相似的方式构造派生类。您可能有更多的参数,更少的参数,它们的顺序可能不同或根本不相关。
class myFile(object):
def __init__(self, filename, mode):
self.f = open(filename, mode)
class readFile(myFile):
def __init__(self, filename):
super(readFile, self).__init__(filename, "r")
class tempFile(myFile):
def __init__(self, mode):
super(tempFile, self).__init__("/tmp/file", mode)
class wordsFile(myFile):
def __init__(self, language):
super(wordsFile, self).__init__("/usr/share/dict/%s" % language, "r")
这适用于所有派生方法,而不仅仅是__init__
。
回答 2
Java和C ++ 要求由于内存布局而调用基类构造函数。
如果您有一个BaseClass
包含成员的类field1
,并且创建了一个SubClass
添加成员的新类field2
,则的实例SubClass
包含field1
和的空间field2
。您需要一个的构造函数BaseClass
来填充field1
,除非您需要所有继承的类BaseClass
在其自己的构造函数中重复的初始化。如果field1
是私有的,那么继承类将无法初始化field1
。
Python不是Java或C ++。所有用户定义类的所有实例都具有相同的“形状”。它们基本上只是可以在其中插入属性的字典。在完成任何初始化之前,所有用户定义类的所有实例几乎完全相同;它们只是存储尚未存储的属性的地方。
因此,对于Python子类而言,不调用其基类构造函数是很有意义的。如果需要,它可以只添加属性本身。对于层次结构中的每个类,没有为给定数目的字段保留空间,并且通过BaseClass
方法中的代码添加的属性与通过方法中的代码添加的属性之间没有区别SubClass
。
如果像通常一样,SubClass
实际上确实希望BaseClass
在继续进行自己的自定义之前设置所有的不变式,那么可以调用BaseClass.__init__()
(或使用)super
,但这是很复杂的,并且有时会出现自己的问题。但是您不必。您可以在之前,之后或使用其他参数来执行此操作。地狱,如果你想的话,可以BaseClass.__init__
完全从另一个方法而不是__init__
; 调用。也许您正在进行一些奇怪的懒惰初始化操作。
Python通过保持简单而实现了这种灵活性。通过编写在__init__
上设置属性的方法来初始化对象self
。而已。它的行为与方法完全一样,因为它正是方法。对于必须首先完成的事情,或者如果您不执行其他操作会自动发生的事情,没有其他奇怪而又不直观的规则。它唯一需要服务的目的是成为一个在对象初始化期间执行的钩子,以设置初始属性值,而它正是这样做的。如果您希望它做其他事情,则可以在代码中显式地编写。
回答 3
“显式比隐式好。” 同样的道理表明我们应该明确地写出“自我”。
最后,我认为这是有好处的-您能列举一下Java关于调用超类的构造函数的所有规则吗?
回答 4
通常,子类具有无法传递给超类的额外参数。
回答 5
现在,我们有一个较长的页面描述了多重继承的情况下方法解析的顺序:http : //www.python.org/download/releases/2.3/mro/
如果自动调用了构造函数,则需要另一页至少具有相同长度的页面,以解释其发生的顺序。那将是地狱…
回答 6
为了避免混淆,知道__init__()
child_class没有__init__()
类时可以调用base_class 方法是很有用的。
例:
class parent:
def __init__(self, a=1, b=0):
self.a = a
self.b = b
class child(parent):
def me(self):
pass
p = child(5, 4)
q = child(7)
z= child()
print p.a # prints 5
print q.b # prints 0
print z.a # prints 1
实际上,__init__()
当在子类中找不到它时,python中的MRO会在父类中查找。如果子类中已经有一个__init__()
方法,则需要直接调用父类的构造函数。
例如,以下代码将返回错误:class parent:def init(self,a = 1,b = 0):self.a = a self.b = b
class child(parent):
def __init__(self):
pass
def me(self):
pass
p = child(5, 4) # Error: constructor gets one argument 3 is provided.
q = child(7) # Error: constructor gets one argument 2 is provided.
z= child()
print z.a # Error: No attribute named as a can be found.
回答 7
也许__init__
是子类需要重写的方法。有时,子类在添加特定于类的代码之前需要运行父级函数,而有时,它们需要在调用父级函数之前设置实例变量。由于Python不可能知道何时最适合调用这些函数,因此不应该猜测。
如果这些都不影响您,请考虑这__init__
只是另一个功能。如果有问题的函数dostuff
代替了,您是否仍然希望Python在父类中自动调用相应的函数?
回答 8
我相信这里一个非常重要的考虑因素是,通过自动调用super.__init__()
,您可以按设计禁止在何时调用该初始化方法以及使用哪些参数。避免自动调用它,并要求程序员明确地执行该调用,需要很大的灵活性。
毕竟,仅因为类B派生自类A并不意味着A.__init__()
可以或应该使用与相同的参数进行调用B.__init__()
。将调用明确化意味着程序员可以例如B.__init__()
使用完全不同的参数进行定义,使用该数据进行一些计算,A.__init__()
使用适合该方法的参数进行调用,然后进行一些后处理。如果A.__init__()
要B.__init__()
在B.__init__()
执行之前或执行之后隐式调用,则很难获得这种灵活性。