本节我们只讨论一种情况:将某模块卸载时,如果某全局变量含有__del__成员函数,其行为是怎样的。

废话少说,先上代码:

#-*- encoding: utf-8 -*-

import sys

class Test(object):
    testCount = 0
    def __init__(self):
        Test.testCount += 1
        print Test.testCount

    def __del__(self):
        print '[{}]'.format(id(self)), {k: None if v is None else id(v) for k, v in globals().iteritems()}
        #由于异常输出的优先级>标准输出,此处强制输出到控制台,以防止下面执行出错时,把错误信息打印到print前面,从而影响到测试效果
        sys.stdout.flush()
        print Test.testCount
        Test.testCount -= 1

a = Test()
#在a, z之前移除
_k = Test()
z = Test()

#输出全局变量在字典中的顺序(确保变量出现的顺序为a < _k < Test < z)
print list(globals())
print '---[a = {}, _k = {}, z = {}]---'.format(id(a), id(_k), id(z))




#当前模块卸载时,按如下顺序删除globals中的变量:
#1. 优先将“单下划线”开头的变量删除
#2. 按照globals中的顺序去删除全局变量
#还要注意:此处说的删除不是将变量从globals()中del掉,而是将变量置为None
 
程序的输出为:
1
2
3
['a', '__builtins__','__file__', '__package__', 'sys', '_k', 'Test', '__name__', 'z', '__doc__']
---[a = 41777640,  _k = 41777696, z = 41778704]---
[41777696] {'a':41777640L, '__builtins__': 6004952L, '__package__': None, 'sys': 6006056L,'_k': None, 'Test': 41880520L, '__name__': 6328752L, 'z':41778704L, '__doc__': None}
3
[41777640] {'a': None, '__builtins__': 6004952L, '__package__': None, 'sys': 6006056L, '_k':None, 'Test': 41880520L, '__name__': 6328752L, 'z': 41778704L, '__doc__': None}
2
[41778704] {'a': None,'__builtins__': 6004952L, '__package__': None, 'sys': None, '_k': None, 'Test':None, '__name__': None,'z': None, '__doc__':None}
Exception AttributeError:"'NoneType' object has no attribute 'testCount'" in <bound methodTest.__del__ of <__main__.Test object at 0x00000000027D7E10>> ignored

 

我们使用红色表示变量a, 绿色表示_k, 蓝色表示z

可以看到删除的顺序是_k > a > Test >z

 

值得一提的有以下几点:

1.      执行某变量的__del__时,如a, globals()中已将a置为了None,我们也可以理解为python是通过globals()['a'] = None动作在当前模块的globals中删除a的。

2.     内置模块__builtins__永远不会被卸载,它会贯穿于整个程序的生命周期。任何时候使用内置成员都是OK的。

3.      由于Test在globals()中位于z之前,在删除z前,Test已经被删除了。所以在删除z时,抛出了异常,因为None.testCount不存在。

4.      C++程序员应注意,Python的None不是nullptr(或NULL)。 None是python的一个对象,它有具体的地址。如果对None应用id函数,id(None)在笔者的Win7x64版上返回506033800L。

5.     请慎用__del__,根据Python官方文档,含有__del__函数的类,其实例不参与“循环垃圾”回收。python中有两种垃圾回收(内存释放)方式,一种是引用计数降为零时,释放该变量;另一种是循环垃圾回收,这类似于Java的GC,主要用于回收由于“循环引用”而使得引用计数无法降为零的变量。而含用__del__成员函数的变量,无法被循环垃圾回收处理,从而造成内存隐患。


那么如何保证__del__中调用Test.testCount不产生异常呢?

根据上面的分析,我们知道Test.testCount之所以失败是因为globals()['Test'] is None了,那么类级变量Test.testCount到底被回收了吗? 显然没有,因为还有类的实例z的存在,在类的所有实例被销毁前,类级变量是不会被回收的(这是Python语言本身的机制)。那如何访问该变量呢,使用self.__class__替换Test,见代码:

def __del__(self):
        print '[{}]'.format(id(self)), {k: None if v is None else id(v) for k, v in globals().iteritems()}
        #由于异常输出的优先级>标准输出,此处强制输出到控制台,以防止下面执行出错时,把错误信息打印到print前面,从而影响到测试效果
        if sys is not None:
            sys.stdout.flush()
        print self.__class__.testCount
        self.__class__.testCount -= 1

__del__是一个比较特殊的函数,它发生在对象销毁时,这个时间可能是由于引用计数降为0,也可能是模块卸载时,或者其它...,如果__del__需要使用self以外的变量的话要格外谨慎格外谨慎,一定要确保该变量有效。

所以要慎用__del__,能不用尽量不用