最近我在学习Python,浏览了优秀的Dive into Python,作者在这里指出,__init__
方法虽然通常像构造函数一样工作,但在技术上不是构造函数。
我有两个问题:
C++如何构造一个对象以及Python如何“构造”一个对象之间有哪些差异?
什么使得构造函数成为构造函数,
__init__
方法又是如何未达到此标准的?
最近我在学习Python,浏览了优秀的Dive into Python,作者在这里指出,__init__
方法虽然通常像构造函数一样工作,但在技术上不是构造函数。
我有两个问题:
C++如何构造一个对象以及Python如何“构造”一个对象之间有哪些差异?
什么使得构造函数成为构造函数,__init__
方法又是如何未达到此标准的?
作者区分的是,就Python语言而言,在进入__init__
之前,您已经拥有了指定类型的有效对象。因此,它不是一个“构造函数”,因为在C++中和理论上,构造函数将无效的、预构造的对象转换为该类型的“适当”完成的对象。
基本上,Python中的__new__
被定义为返回“新对象实例”,而C++ new操作符只返回一些内存,这还不是任何类的实例。
然而,在Python中,__init__
可能是您首次创建一些重要类不变量(仅从起始点来看)。所以就用户类而言,它可能就是构造函数。只是Python运行时不关心这些不变式。如果您愿意,它对于构成构造好的对象具有非常低的标准。
我认为作者的观点很公正,并且它确实是关于Python创建对象方式的有趣评论。不过这是一个微妙而微小的区别,我怀疑将__init__
称为构造函数会导致错误代码。
此外,我注意到Python文档将__init__
称为构造函数(http://docs.python.org/release/2.5.2/ref/customization.html)
作为构造函数的一个特殊限制,不能返回任何值
.....所以如果将__init__
视为构造函数会导致任何实际问题,那么Python就会遇到麻烦!
Python和C ++构建对象方式有一些相似之处。两者都调用具有相对简单职责的函数(对象实例的__new__
vs原始内存的某个版本的operator new
),然后两者都调用可以更多地初始化对象进入可用状态的函数(__init__
vs构造函数)。
实际差异包括:
在C++中,如果需要,基类的无参构造函数会按照适当的顺序自动调用,而在Python的__init__
中,您需要在自己的__init__
中显式地初始化您的基类。即使在C++中,如果基类构造函数有参数,您也需要指定它。
在C++中,当构造函数抛出异常时,您有整个机制来处理已经构造的子对象的析构函数调用。在Python中,我认为运行时(最多)只调用__del__
。
此外,还有一个区别是__new__
不仅仅分配内存,它还必须返回一个实际的对象实例。然而,在Python代码中,原始内存并不是一个真正适用的概念。
__del__
,它只是向上传播异常,并在最后一个对正在初始化的对象的引用消失后(最终)销毁该对象。__del__
可能 在此之前被调用,但实际上并未用于执行任何析构操作;__del__
是 __init__
的对应项,而不是 __new__
:) - Thomas Wouters__del__
真的是__init__
的对应物,那么我期望它被定义为只有在__init__
完成时才会被调用。当然,它可能会释放在__init__
或类的任何其他方法中分配的系统资源。我的有限理解是,在CPython中,无论__init__
是否完成甚至是否被调用,都会在对象被销毁时调用__del__
。而且,未能成功执行__init__
的对象将立即被销毁,尽管这是CPython引用计数的实现细节。这样对吗? - Steve Jessop__del__
。但是我并不想过多地关注__del__
。我从未使用过它,并且就其保证的行为而言,它对我来说似乎与Java finalizer一样有用,即不是很有用。 - Steve Jessop__del__
不是__init__
的严格对应物。它不与__init__
成功执行绑定。它是__init__
的对应物,因为它是一个可选的钩子,在对象销毁之前被调用,就像__init__
是一个可选的钩子,在对象构造之后右侧被调用。 __del__
并不会被调用以执行对象销毁,而且__del__
方法实际上可以防止对象销毁(通过在某处存储对self
的引用)。 是的,这真的没有用,并且它可能会创建无法清理的引用循环,这非常糟糕。依靠Python来进行清理更好。 - Thomas Wouters__new__
创建对象,并通过__init__
修改该通用默认对象。而__init__
是一个普通方法,特别的是它可以被虚拟地调用,从__init__
中调用的方法也会被虚拟调用。operator new
动态分配,或者作为另一个对象的一部分分配。然后实例化类型的构造函数将原始内存初始化为适当的值。给定类的构造函数自动调用基类和成员的构造函数,因此构造保证是“自下而上”的构造方式,优先创建部分。T
的构造函数体执行期间,该对象的类型为T
,因此对于虚拟方法的调用将解析为T
类型的对象(此时确实如此),其中T
可以是您实例化的类的基类。T
的每个对象中,只有一个T
构造函数调用。我曾将其称之为单构造函数调用保证(single constructor call guarantee)。它没有在标准的任何地方指定,您可以通过使用语言的非常低级的功能来挫败它,但是它存在,并且标准的详细规则旨在实现它(这与您不会在任何地方找到有关语句分号结束的单个规则非常相似,然而各种语句的无数语法规则共同产生了一个简单的高级规则)。__new__()
的工作。__init__()
只是一个初始化方法。operator new
的工作。或者在放置new的退化情况下,实际上是调用者的工作告诉new版本内存所在的位置,以便它可以将该值回显给您。 - Steve Jessop这种差异是由于Python的动态类型。与C++不同,C++中变量是带有类型声明并在内存中分配的,而Python的变量是在运行时赋值时创建的。C++的无参构造函数会自动调用,以便初始化数据成员。在Python中,它是按需完成的,并且__init__
是通过继承树查找的,只有最低的一个被调用一次。如果需要超类数据属性,则像C++ initialization list那样显式调用super().__init__()
。下面是一个示例,其中初始化了Base.s:
class Base:
def __init__(self, base):
print("Base class init called")
self.base = base
class Super(Base):
def __init__(self, base):
print("Super class init called")
#super(Super, self).__init__(base)
super().__init__(base)
class Sub(Super):
def __init__(self, base):
print("Default __init__ is called")
super().__init__(base)
sub = Sub({"base3": 3, "base4": 4})
print(sub.__dict__, sub.base)
输出:
Default __init__ is called
Super class init called
Base class init called
{'base': {'base3': 3, 'base4': 4}} {'base3': 3, 'base4': 4}
Sub.__init__(sub, 'abc')
print(sub.__dict__, sub.base)
输出:
Default __init__ is called
Super class init called
Base class init called
{'base': 'abc'} abc
__init__
是一个构造函数,因为它分配值给属性并填充/完成实例。 - Rafe Kettlernew
时,它会为您分配内存,然后在该内存上运行构造函数。重要的是,分配和构造是完全不同的操作。请注意,任何对象(一般而言)都可以在堆栈或堆中分配,并且构造函数是唯一的。 - David Rodríguez - dribeas