解决方案-关于python在析构函数中无法使用open函数
class Test:
def __init__(self):
print("__init__")
def main(self):
print("main")
def __del__(self):
with open("test.txt", "w") as f:
f.write("hhhhh")
print("__del__")
t = Test()
t.main()
会报错 NameError: name ‘open’ is not defined
python2没有这个问题
本文围绕该问题初步探究Python垃圾清理的机制
文章目录
解决方案一 try …except …finally
情景: 我的需求是在对象销毁时, 要在文件中进行一个记录
解决: 使用 try...except...finally...
try: # 在Test类中移除open()函数, 实例化对象交由try
t = Test()
t.main()
except Exception as e:
print(e)
finally: # open()函数交由finally
with open("test.txt", "w") as f:
f.write("20201214")
解决方案二 转移open()函数
在__del__中无法使用open()函数, 将open()转移到其它对象方法中, 最后在析构函数中关闭
class Test:
def __init__(self):
print("__init__")
def main(self):
print("main")
self.file = open("test.txt", "w")
def __del__(self):
self.file.write("helloworld")
print("__del__")
self.file.close()
t = Test()
t.main()
解决方案三 使用atexit模块
在百度和必应中搜寻无果, 果然还是google更加强大!
为解决这一问题, 我们可以首先导入atexit模块, 然后将析构操作写入另一个函数中, 最后用atexit.register来注册这个函数. 比如, 以上程序可以改写如下:
import atexit
class 存储代理:
def __init__(self):
atexit.register(self.cleanup)
print("开始")
def cleanup(self):
print("打开文件前")
with open("./测试.txt", "w") as 文件:
pass
print("打开文件后")
def __del__(self):
pass
代理 = 存储代理()
atexit模块
atexit-允许程序员定义在正常程序终止时执行的多个退出函数。 at exit python atexit 模块定义了一个 register 函数,用于在 python 解释器中注册一个退出函数,这个函数在解释器正常终止时自动执行,一般用来做一些资源清理的操作。atexit 按注册的相反顺序执行这些函数; 例如注册A、B、C,在解释器终止时按顺序C,B,A运行。
Note:如果程序是非正常crash,或者通过os._exit()
退出,注册的退出函数将不会被调用。
atexit.register(func, *args, **kargs)
将func作为要在终止时执行的函数。任何要传递给func的可选参数必须作为参数传递给register()
。可以多次注册相同的函数和参数。
当程序退出的时候,按先进后出的顺序调用注册的函数。如果退出函数在执行过程中抛出了异常,atexit会打印异常的信息,并继续执行下一下回调,直到所有退出函数执行完毕,它会重新抛出最后接收到的异常。
一.通过装饰器使用atexit
from atexit import register
def main():
print('Do something.')
@register
def _atexit():
print('Done.')
if __name__ == '__main__':
main()
输出: Do something
Done
二.非装饰器方式
from atexit import register
def main():
print('main')
def goodbye(name, adjective):
print('Goodbye, %s, it was %s to meet you.' % (name, adjective))
register(goodbye, 'Donny', 'nice')
# register(goodbye, adjective='nice', name='Donny')
if __name__ == '__main__':
main()
输出: main
Goodbye, Donny, it was nice to meet you.
文章目录
解决方案五 显式del
class Test:
def __init__(self):
print("__init__")
def __del__(self):
with open("xianshi.txt", "w") as f:
f.write("ffff")
t = Test()
del t
基于本例的问题探究
dir() 函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表。如果参数包含方法__dir__(),该方法将被调用。如果参数不包含__dir__(),该方法将最大限度地收集参数信息。
所以, 利用dir()函数, 我想探究在析构函数中到底减少了哪些方法和属性
class Test:
def __init__(self):
print(dir(__builtins__), len(dir(__builtins__)))
print("__init__")
def __del__(self):
print("---del---")
print(dir(__builtins__), len(dir(__builtins__)))
print("---del---")
with open("test.txt", "w") as f:
f.write("ffff")
t = Test()
# del t
结果分析: 在__init__(self)中, 内建属性和方法有153个, 然而在__del__中只有146个, 减少了7个
我使用集合经行相减后发现, 缺失的内建有
{'quit', 'license', 'credits', 'exit', 'help', 'open', 'copyright'}
在交互式的python中, 内建属性方法有154个
如果上述代码的第14行取消注释, 在析构函数中的内建属性和方法仍然有153个
个人看法
如果 手动del对象, 此时python内的资源还健全,要是等到python解释器来清理, 它已经清理了太多资源,所以当执行到一些已清理的资源时便会报错
解决方案六
class Iam(object):
def __init__(self):
print("I m born")
def __del__(self):
#"open" function still in __builtins__
f = open("memory_report.txt", "w")
f.write("He gone safe")
f.close()
def write_iam():
print("----")
i=Iam()
print("====")
if __name__ == '__main__':
write_iam()
print("Script Ends. Now to GC clean memory")
主体程序结束后才调用 Iam类__del__方法,而不是当 i超出 write_iam的作用域时就去立即调用__del__方法。
原理如下:
Python 采用的是 引用计数机制为主, 标记-清除和分代收集两种机制为辅的策略。 因此, 当 Python 在超出范围时不会清理它,只有当它的最后一次引用超出范围时, 才会将它清理掉。