Python 会自动管理内存,因此大多数对象不需要显式 cleanup。 但如果一个对象拥有外部资源(文件、socket、锁), 你必须释放它。
__del__() 不是 CleanupPython 在销毁对象时会调用它的 __del__() method。 这看起来似乎正是释放资源的位置:
# cleanup.py
from typing import ClassVar
class Counter:
# Number of objects of this class
count: ClassVar[int] = 0
def __init__(self, name: str) -> None:
self.name = name
print(name, "created")
Counter.count += 1
def __del__(self) -> None:
print(self.name, "deleted")
Counter.count -= 1
if Counter.count == 0:
print("Last Counter object deleted")
else:
print(Counter.count,
"Counter objects remaining")
def __repr__(self) -> str:
return f"Counter({self.name!r}{self.count})"
counters = []
for name in ["First", "Second", "Third"]:
counters.append(Counter(name))
#: First created
#: Second created
#: Third created
for c in counters:
print(c)
del c
#: Counter('First' 3)
#: Counter('Second' 3)
#: Counter('Third' 3)
print("End of delete loop")
#: End of delete loop
循环内部的 del c 并不会删除对象。 它只是解除名称 c 的绑定,并释放这个名称持有的那一个 reference。 counters list 仍然引用着每一个 Counter, 所以循环期间没有任何 Counter 的 reference count 降到零。 这就是为什么循环运行时不会出现任何 deleted 行, 为什么每一个 __repr__() 都报告 count 为 3, 以及为什么 End of delete loop 会在任何 deletion 之前打印。
Python 会在之后、interpreter shutdown 时销毁这些对象, 因为那时它会拆除 global counters list。 这个 list 持有仅剩的 reference,所以它消失时,其中对象也随之消失。 代码清单在 End of delete loop——程序最后一条 statement——处结束, 而每个 __del__() 都只在之后才打印。 如果直接运行 cleanup.py, 上面最后一行之后还会出现三组内容:
Third deleted
2 Counter objects remaining
Second deleted
1 Counter objects remaining
First deleted
Last Counter object deleted
这只是某一台机器上的一次运行结果。 如果在本书的 output checker 下运行同一个文件——它会在单个 process 中执行每章—— 三个对象的 finalization 顺序恰好相反。
三个 __del__() 的执行顺序属于 implementation detail。 它取决于 interpreter 在 shutdown 时如何拆除 counters list, 不同 CPython build 之间都可能不同。 其他实现,例如采用 tracing garbage collector 的 PyPy, 可能以完全不同的顺序销毁对象, 甚至在退出之前根本不执行这些 finalizer。
因此 __del__() 很脆弱: 语言既不保证它什么时候运行,也不保证它一定运行。 在 interpreter shutdown 期间, 一个 __del__() 所引用的 global 可能已经消失。 Python documentation 警告:
警告:由于
__del__()method 被调用时所处的环境并不可靠, 其执行期间发生的异常会被忽略,并改为向sys.stderr打印警告。尤其需要注意:
__del__()可能在任意代码正在执行时被调用, 甚至可能来自任意线程。如果__del__()需要获取锁或调用其他会阻塞的资源, 就可能发生 deadlock,因为触发__del__()、被中断的那段代码可能已经占用了该资源。__del__()可能在 interpreter shutdown 期间执行。因此, 它需要访问的 global variable(包括其他 module)可能已经被删除或设置成None。 Python 保证名称以单下划线开头的 global 会先于该 module 中其他 global 被删除; 如果这些 global 没有其他 reference,这可能有助于确保在调用__del__()时, 已导入 module 仍然可用。
在直接运行的情况下,是 shutdown 过程销毁对象, 这正是上面 warning 所描述的不可靠时刻。 当时 Counter 和 print() 恰好仍然可用,所以输出看起来很正常, 但没有任何东西保证允许这种结果的 teardown order。 __del__() 应尽量少做事情,而且代码不应依赖它。
真正会在生产环境里付出高昂代价的,是异常被吞掉:
# del_swallows.py
class Resource:
def __init__(self, name: str) -> None:
self.name = name
def __del__(self) -> None:
raise RuntimeError(f"{self.name} not released")
resource = Resource("db")
del resource
print("still running")
#: still running
资源释放失败,但 stdout 什么也没说。 一个带 Exception ignored 标签的 traceback 会输出到 sys.stderr, 可是什么都不会向上传播:没有 caller 能捕获它,没有 finally 会因此运行, exit status 仍然是 0,而一个只断言 stdout 的测试仍然会通过。 相比之下,在 with block 中调用 close() 如果失败,会明确地把错误暴露出来。
不可预测的时机并不是 __del__() 唯一的问题。 如果对象直接或间接引用自身, 它不会在变得 unreachable 的那一刻立即消失:
# cycle.py
import gc
class Node:
peer: Node
def __init__(self, name: str) -> None:
self.name = name
def __del__(self) -> None:
print(self.name, "finalized")
def self_link() -> None:
node = Node("a")
node.peer = node
gc.disable()
self_link()
print("unreachable, but still alive")
#: unreachable, but still alive
gc.collect()
#: a finalized
gc.enable()
print("after collect")
#: after collect