众所周知,从面向对象程序设计角度来讲,在Python语言中,不管类的名字是什么,构造方法的名字统一为__init__(),在创建对象时自动调用,用来对数据成员进行初始化;析构方法的名字统一为__del__(),用来释放对象占用的内存空间,在删除对象时自动调用。
创建对象的时机是很显然的,但对象被删除的时机并不像表面看上去那么简单。
为了演示和解释这个问题,我们编写下面的代码并在IDLE中运行:
从上面的运行结果来看,只有对象的构造方法被调用了,并没有调用析构方法。
但是在命令提示符环境执行这个程序时,析构方法又被调用了,在PyCharm或其他类似的开发环境中运行程序时也会得到下面的结果。
原因在哪里呢?
在命令提示符环境、PyCharm或类似环境中,是以独立进程的方式运行程序的,程序运行完的适合进程也就结束了,这时候会释放进程中所有资源,包括自己创建的所有对象,所以析构方法被调用。但在IDLE环境中,IDLE为主线程,自己的程序运行结束后IDLE主线程尚未结束,创建的对象不会自动删除,除非自己显式使用del关键字删除对象。
为了验证这个问题,在上面代码最后增加删除对象的代码,在IDLE环境中也会自动调用析构方法。
既然用到了del,我们就顺便来解释一下这个关键字的真正作用:删除变量,并解除变量与值之间的引用关系,值的引用次数减1。
在Python中,变量不直接存储值,而是存储值的引用或者内存地址,列表、元组、字典、集合、字符串等容器类对象中的元素也是如此。variable = value这样的语句并不是把value赋值给variable,而是使得variable引用value的值,也可以理解为在存放value值的内存空间贴上标签variable。
例如,x = 3这样的语句执行过程为:在内存中查找存放3的位置,在该内存上贴上标签x。当再执行x = 5语句时,把标签x从存放3的内存上撕下来然后贴到存放5的内存空间上。这也是Python中变量类型可以任意改变的原因。
如果value不是字面值而是另一个变量,那么variable = value语句的作用是使得variable具有和value相同的引用,或者说使得variable和value引用同一个内存空间,或者说给同一个内存空间贴了两个标签,此时两个变量的identity相同,使用is测试两个变量时得到True。当然,同一个内存空间可以贴任意多个标签,也就是可以使得任意多个变量引用同一个内存空间。
Python采用的是基于值的内存管理模式,在同一个程序中或交互模式下同一条语句中相同的值在内存中只保留一份,详见:Python基于值的内存管理真相。在解释器内部为每个值记录了一个引用次数,也就是当前有多少个变量在引用这个值,或者说这个对象的内存空间上被贴了多少个标签。可以使用标准库sys中的函数getrefcount()查看一个对象的当前引用次数。
当我们使用关键字del删除一个变量时,同时会解除这个变量与值之间的引用关系,从相应的内存空间上撕掉一个标签,值的引用次数减1。当引用次数变为0时,Python的垃圾回收机制就会从内存中删除这个值,回收相应的内存空间。所以,当多个变量引用同一个对象时,使用del删除其中部分变量时,并不会调用对象的析构方法。
只有当引用同一个对象的所有变量都删除之后,对象的引用次数变为0时,才会真正删除对象、调用析构方法、释放内存空间。
另外,除非使用关键字global进行声明,在函数中创建的对象均为局部变量,函数执行结束后操作系统会回收为该函数分配的栈帧,该函数中创建的所有局部变量都会被释放(不需要显式使用del关键字删除),自然也就会调用对象的析构方法。改写上面的代码如下,并在IDLE环境中运行,其他环境均会得到同样的结果。
建议:对于需要长时间运行的程序,尤其是服务端程序,使用关键字del显式删除不再使用的变量,可以及时释放资源,减轻服务器压力。