今天碰上了一个问题,先上代码

1
2
3
4
5
6
7
8
9
10
11
import xlsxwriter

class A:
def __init__(self):
self.work = xlsxwriter.Workbook('a.xlsx')

def __del__(self):
print('del')
self.work.close()

a = A()

也没做什么特殊操作,就是在析构的时候调用一下workbookclose而已,然而这段代码会报一个错误

1
2
3
4
5
6
7
8
9
10
11
12
Exception ignored in: <bound method A.__del__ of <__main__.A object at 0x103633588>>
Traceback (most recent call last):
File "/Users/svz/code/python/test/t/a.py", line 10, in __del__
File "/usr/local/lib/python3.6/site-packages/xlsxwriter/workbook.py", line 306, in close
File "/usr/local/lib/python3.6/site-packages/xlsxwriter/workbook.py", line 649, in _store_workbook
File "/usr/local/lib/python3.6/site-packages/xlsxwriter/packager.py", line 132, in _create_package
File "/usr/local/lib/python3.6/site-packages/xlsxwriter/packager.py", line 189, in _write_worksheet_files
File "/usr/local/lib/python3.6/site-packages/xlsxwriter/xmlwriter.py", line 41, in _set_xml_writer
File "/usr/local/Cellar/python/3.6.5_1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/codecs.py", line 897, in open
AttributeError: module 'builtins' has no attribute 'open'
# 或者是另外一段错误,这是在另外一台电脑上执行的结果
ModuleNotFoundError: import of re halted; None in sys.modules

这就引发了我的好奇了,为什么直接调用close()不出错,但是在__del__里调用close()会出现错误呢,而且看报错信息都是和Module相关的?
查看了一些文档经过一些测试之后,发现问题还是出在__del__上面

首先我们得先知道__del__是在什么时候调用的呢,我们看看官方的说法

Called when the instance is about to be destroyed. This is also called a finalizer or (improperly) a destructor. If a base class has a del() method, the derived class’s del() method, if any, must explicitly call it to ensure proper deletion of the base class part of the instance.
It is possible (though not recommended!) for the del() method to postpone destruction of the instance by creating a new reference to it. This is called object resurrection. It is implementation-dependent whether del() is called a second time when a resurrected object is about to be destroyed; the current CPython implementation only calls it once.
It is not guaranteed that del() methods are called for objects that still exist when the interpreter exits.
Note del x doesn’t directly call x.del() — the former decrements the reference count for x by one, and the latter is only called when x’s reference count reaches zero.
CPython implementation detail: It is possible for a reference cycle to prevent the reference count of an object from going to zero. In this case, the cycle will be later detected and deleted by the cyclic garbage collector. A common cause of reference cycles is when an exception has been caught in a local variable. The frame’s locals then reference the exception, which references its own traceback, which references the locals of all frames caught in the traceback.
See also Documentation for the gc module.
Warning Due to the precarious circumstances under which del() methods are invoked, exceptions that occur during their execution are ignored, and a warning is printed to sys.stderr instead. In particular:
del() can be invoked when arbitrary code is being executed, including from any arbitrary thread. If del() needs to take a lock or invoke any other blocking resource, it may deadlock as the resource may already be taken by the code that gets interrupted to execute del().
del() can be executed during interpreter shutdown. As a consequence, the global variables it needs to access (including other modules) may already have been deleted or set to None. Python guarantees that globals whose name begins with a single underscore are deleted from their module before other globals are deleted; if no other references to such globals exist, this may help in assuring that imported modules are still available at the time when the del() method is called.

注意看这一段

can be executed during interpreter shutdown. As a consequence, the global variables it needs to access (including other modules) may already have been deleted or set to None

什么意思呢,意思就是__del__可以在解释器关闭期间执行,所以它可能已经删除了(设置为None)一些需要访问的全局变量(包括其它的模块)
所以就是说,按照上面的写法,可能在执行__del__的时候,一些其它的模块已经被干掉了,所以builtins里的open已经被干掉了,所以会报找不到这个方法的错误。
下面的re被干掉又是为什么呢?
python导入模块之后会在sys.module中添加上对应的信息,所以在sys被干掉的时候,这个信息也没了,所以re也被干掉了
下面上验证代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import sys
import xlsxwriter

print(sys.modules)
class A:
def __init__(self):
self.work = xlsxwriter.Workbook('a.xlsx')

def __del__(self):
print('del')
print(sys.modules)
self.work.close()

a = A()
print('end')

这段代码执行结果

1
2
3
4
{'builtins': <module 'builtins' (built-in)>, 'sys': <module 'sys' (built-in)>, '_frozen_importlib': <module 'importlib._bootstrap' (frozen)>, '_imp': <module '_imp' (built-in)>, '_warnings': <module '_warnings' (built-in)>, '_thread': <module '_thread' (built-in)>...}#太长了省略一下
end
del
{}

可以看到end会在del之前输出,也就是已经开始关闭解释器了,并且delsys.modules已经是空的了

怎么可以避免这个问题呢?
第一种手动去调用del a
第二种就是这么写

1
2
3
4
def test():
a=A()

test()

这样的话a是一个局部变量,函数结束的时候就会释放掉
总之,就是确保在解释器关闭之前释放