Python Cleanup
截止于 Python 3.13
前言
Python 资源自动释放的机制就是四种:with
, __del__
, atexit
, weakref.finalize
with
适用于局部资源管理,是主流的资源自动释放手段,还有方便编写的 contextlib.contextmanager
装饰器。
但是对于经典的单例、全局资源类不适用。
__del__
即编写对象的钩子函数 __del__
,但是 Python 没有为它提供任何有力的保证,语义实现的机制非常脆弱,是一种早期失败的设计。
它有以下几个特点:
苏生 UB
在方法内部创建新的对象,从而为它的指针计数器加一,这个做法称为苏生(resurrection),但当这个新的对象被销毁时 __del__
是否会被调用是 Implement Dependent 。
非确定性执行
不保证解释器退出的时候会被执行。
用户负责
用户代码里注意不要申请可能会造成死锁的资源。
用户自己注意不要引用可能已被销毁的全局变量。1
结论
总之就是失败设计,不如不不用。
atexit
确定在解释器退出时会被调用,是静态生命周期、全局资源类选项。
atexit.register(Callable[[], Any])
没有提供针对资源类的方法装饰器,只能在 __init___
里手动加上一行类似 register(self.cleanup)
的代码。
而注册了类方法之后,相当于让这个资源类永远有一个引用在那里,导致它永远不会被回收,相当于把资源类的生命周期提高到了 static
。
weakref.finalize
作为 __del__
的上位替代,在资源回收(gc)的时候调用,解决了 __del__
的在解释器退出时不确定执行的问题,但几乎也就仅此而已。
尾声
从编程角度,资源管理只有两个模式,一个是在局部上下文里使用-释放;另一个是使用全局单例模式。
前者是最推荐的设计模式, with
机制已经处理很好了;而后者里是设计上的反模式,是传统上的 tradeoff 的做法,需要自己实现一个机制。
这样全局资源类的资源释放,实际上应该在单例模式的机制里顺便实现。
如下展示如何通过标记 cleanup 方法和使用 atexit.register ,在一个单例模式里实现资源自动释放。
注解
-
Python guarantees that globals whose name begins with a single underscore are deleted from their module before other globals are deleted. ↩