未在 Python 脚本中调用析构函数

Destructor not being called in a Python script

下面是按我预期执行的模块。

class Z():
    def Y(self):
        return
    def __del__(self):
        print('Z deleted.')
def W(v):
    class Form:
        def X(self):
            #v.Y()
            return  
    return
def U():
    t = Z()
    W(t)
U()

运行 上述模块产生以下输出

Z deleted.

当我如下所示删除注释时,没有产生任何输出。

class Z():
    def Y(self):
        return
    def __del__(self):
        print('Z deleted.')
def W(v):
    class Form:
        def X(self):
            v.Y()
            return  
    return
def U():
    t = Z()
    W(t)
U()

为什么不调用析构函数?

我运行这个模块在下面的实用程序中。操作系统是 Windows 10 Pro,版本 1803,OS build 17134.165

您编写的脚本正在以不太明显的方式创建引用循环。非显而易见的循环是 all class 声明本质上循环的结果,因此 Wclass 声明的简单存在意味着那里将是一些循环垃圾。我不确定这是否是所有 Python 解释器的必要条件,但 CPython 的实现(至少从 2.7 到 3.6,我检查过的解释器)确实如此。

在你的 Z 实例中循环并触发你观察到的行为的事情是你使用 v (这是对 Z 实例的引用)闭包范围当您将 Form.x 声明为 class 声明的一部分。闭包范围意味着只要调用 W 定义的 class Form 存在,关闭变量 v(最终是 Z 的一个实例)将保持活动状态.

当您 运行 一个处于空闲状态的模块时,它会 运行 执行该模块的代码后将您转储到交互式提示,但 Python 是仍然 运行ning,因此它不会立即执行任何全局清理或 运行 循环 GC。 Z 的实例将 最终 被清理(至少在 CPython 3.4+ 上),但循环 GC 通常 运行 只有在相当长的一段时间后没有匹配释放的分配数量(我的解释器默认为 700,尽管这是一个实现细节)。但是该收集可能需要任意长的时间(在解释器退出之前执行了最终循环清理,但除此之外,没有任何保证)。

通过注释掉引用 v 的行,您不再关闭 v,因此循环 class 不再使 v 保持活动状态,并且当最后一个引用消失时,v 被及时清理(在 CPython 的引用计数解释器上;在 Jython、PyPy、IronPython 等上没有保证)。

如果你想强制清理,在 运行 模块之后,你可以 运行 在生成的交互 shell 中使用以下命令强制清理第 0 代:

>>> import gc
>>> gc.collect(0)  # Or just gc.collect() for a full cycle collection of all generations

或者只需将相同的行添加到脚本末尾即可自动触发它。