什么时候异常处理会意外地影响对象的生命周期?

When does exception handling unexpectedly influence object lifetimes?

Python reference on the data model 注意到

catching an exception with a ‘try…except’ statement may keep objects alive.

异常会改变控制流,这似乎很明显,可能会导致不同的对象仍然被引用。为什么会明确提到?这里是否存在内存泄漏的可能性?

好吧,据我所知,如果异常引用某个对象或另一个对象,则在异常本身被收集之前不会收集这些对象,而且,如果 except 语句恰好引用了某个对象,那将还对其集合进行后门处理,直到该块结束为止。我想知道是否还有其他不太明显的方法可以捕获异常来影响垃圾回收。

异常存储回溯,它存储 raisingexcepting 之间的所有子帧(“函数调用”)。框架引用所有本地名称及其值,防止对本地名称和值进行垃圾回收。

这意味着异常处理程序应立即完成异常处理,以允许清理子局部变量。尽管如此,一个函数不能依赖于它的局部变量在函数结束后立即被收集。

因此,即使在引用计数实现中,RAII 等模式也不能可靠地提示。当需要快速清理时,对象应该提供一种显式清理(用于 finally 块)或自动清理(用于 with 块)的方法。

Objects, values and types

[…] Programs are strongly recommended to explicitly close such objects. The ‘tryfinally’ statement and the ‘with’ statement provide convenient ways to do this.


可以通过标记垃圾收集时间的 class 来观察这一点。

class Collectible:
    def __init__(self, name):
        self.name = name
    def __del__(self, print=print):
        print("Collecting", self.name)

def inner():
    local_name = Collectible("inner local value")
    raise RuntimeError("This is a drill")

def outer():
    local_name = Collectible("outer local value")
    inner()

try:
    outer()
except RuntimeError as e:
    print(f"handling a {type(e).__name__}: {e}")

在 CPython 上,输出显示处理程序在 收集局部变量之前运行:

handling a RuntimeError: This is a drill
Collecting inner local value
Collecting outer local value

请注意,CPython 使用引用计数,这已经导致尽快进行快速清理。其他实现可能会进一步任意延迟清理。