一、 引言
基本上所有支持OOP设计的语言都支持析构方法(也称析构函数),析构方法都是在对象生命周期结束时调用,一般用来实施实例相关生命周期内访问数据的扫尾工作,包括关闭文件、释放内存、输出日志、清理数据等。
二、 析构方法语法
Python中所有类的析构方法都是特殊方法__del__,析构方法同样是一个实例方法,其语法如下:
del(self)
self就是对象自身,所有实例方法都有该参数,真正调用时无需传递。
析构方法没有返回值要求。
析构方法语法很简单,没有需要过多解释的地方。
三、 析构方法的使用
- 析构方法比较特殊,这是因为它是Python中定义的特殊方法,在对象销毁时自动执行,但在object类内却没有该方法,在自定义类定义时,并没有要求一定要自己定义析构方法,因此在自定义类及其自定义父类中没有定义__del__析构方法时,该类就没有析构方法;
- 如果子类定义了析构方法,其派生的父类也定义了析构方法,则在子类的析构方法中必须调用父类的析构方法;
- 当程序不再需要一个 Python 对象时,系统必须把该对象所占用的内存空间释放出来,这个过程被称为垃圾回收(GC,Garbage Collector),Python 会自动回收所有对象所占用的内存空间,开发者无须关心对象垃圾回收的过程。垃圾回收会自动触发对析构方法的调用;
- 使用del删除实例对象时,并不一定会触发析构方法的调用
1>Python中可能存在多个实例ID相同的情况,如实例a通过赋值语句赋值给b时,此时a与b的ID相同,就认为该ID对象上增加了一个引用;如果一个对象有多个变量引用它,那么 del 其中一个变量是不会回收该对象的。这个销毁的动作还是需要等待对象引用没有了以后才能够完成。
2>Python 采用自动引用计数(ARC:Automatic Reference Counting)方式来判断对象是否需要被回收,当程序中有一个变量引用该对象时,Python 会自动设置该对象引用计数为1;当程序中每增加一个变量引用该 Python 对象(如赋值)时,Python 会自动对该对象引用计数加1,反之如果每减少一个(如del 变量)变量的引用,该对象引用计数就减一,当对象的引用计数变成 0,则说明不再有变量引用该对象,Python 就会回收该对象,此时才会调用析构方法;
3>大部分时候,Python 的 ARC 都能准确、高效地回收系统中的每个对象。但如果系统中出现循环引用的情况,如两个对象下的属性相互指向对方(如双向链表),但两个对象都没有单独的变量引用它们,此时两个对象的引用计数并不是0,而都是1,但实际上程序已经不再有变量引用它们,此时 Python 的垃圾回收器就可能没那么快,要等专门的循环垃圾回收器(Cyclic Garbage Collector)来检测并回收这种引用循环。 - 由于调用 del() 方法时周边状况已不确定,如部分关联对象已经回收、部分模块删除了等,因此Python对在__del__() 方法执行期间发生的异常将被忽略,改为打印一个警告到 sys.stderr;
- 老猿验证在交互模式执行del实例变量时,哪怕对象没有引用了,Python也不一定马上执行垃圾回收和__del__方法,会出现比较复杂的情况,可能在del语句执行一段时间后执行其他语句时突然释放。对此老猿不展开多说,为此老猿下面的代码是通过文件方式执行的。
四、 例子 - 案例说明
本节定义Vehicle类和Car类,后者是从前者派生的。将在定义实例后,将实例赋值给另一个变量,然后使用del删除变量,看看是否能触发析构方法。 - 源代码
class Vehicle():
def __init__(self,wheelcount,power):
self.wheelcount = wheelcount
self.power = power
print("In Vehicle init,objectid = {:#016X}".format(id(self)))
def __del__(self):
print("in Vehicle __del__,delete objectid = {:#016X}".format(id(self)))
print("Vehicle deleted")
class Car(Vehicle):
def __init__(self, power,oilcostper100km):
print("In Car init,objectid = {:#016X}".format(id(self)))
self.oilcostper100km = oilcostper100km
super().__init__(4, power)
def __del__(self):
print("in Car __del__,delete objectid = {:#016X}".format(id(self)))
super().__del__()
print("Car deleted")
print("execut Car('汽油发动机',10)...")
car = Car('汽油发动机',10) #对象引用数为1
c=car #对象引用数为2
print("execut del car...")
del car #对象引用数为1
print("execut del c...")
del c #对象引用数为0,应该触发析构方法
print("Prog end.")
- 源代码截图:
- 执行结果截图:
- 案例总结
通过上述截图可以看出,在引用数为0才触发了析构方法。
最后,老猿需要强调一点,在Python中析构方法的使用要慎重,一是因为del变量不一定触发析构方法,二是因为调用 del() 方法时周边状况已不确定,三是Python有自己的垃圾回收机制执行相关资源的回收和清理,因此编程者意图在析构方法中实施的操作不一定会按照编程者的意图完整实施。
本节对Python析构方法的语法、使用以及注意事项进行了深入介绍,并举例进行了验证,请大家理解,并慎重使用!