Python函数在异常期间的局部变量作用域
背景:我正在用Python进行国家仪器公司的TestStand的COM编程。如果对象没有被“释放”好,TestStand会弹出一个“对象没有被正确释放”的调试对话框。在Python中释放TestStand的COM对象的方法是确保所有变量不再包含这个对象,比如用del()
删除它们,或者把它们设置为None
。或者,只要这些变量是函数内部的局部变量,当函数结束时,变量超出作用域,对象就会被释放。
我在我的程序中遵循了这个规则,只要没有异常,我的程序就能正确释放对象。但如果出现异常,我就会收到TestStand的“对象没有被释放”的消息。这似乎表明,当发生异常时,函数的局部变量没有正常超出作用域。
这里有一个简化的代码示例:
class TestObject(object):
def __init__(self, name):
self.name = name
print("Init " + self.name)
def __del__(self):
print("Del " + self.name)
def test_func(parameter):
local_variable = parameter
try:
pass
# raise Exception("Test exception")
finally:
pass
# local_variable = None
# parameter = None
outer_object = TestObject('outer_object')
try:
inner_object = TestObject('inner_object')
try:
test_func(inner_object)
finally:
inner_object = None
finally:
outer_object = None
当这个代码按预期运行时,结果是我所期待的:
Init outer_object
Init inner_object
Del inner_object
Del outer_object
但是如果我取消注释raise Exception...
这一行,我得到的结果是:
Init outer_object
Init inner_object
Del outer_object
Traceback (most recent call last):
...
Exception: Test exception
Del inner_object
由于异常,inner_object
的删除被延迟了。
如果我取消注释将parameter
和local_variable
都设置为None
的那几行,那么我就得到了我所期待的结果:
Init outer_object
Init inner_object
Del inner_object
Del outer_object
Traceback (most recent call last):
...
Exception: Test exception
所以,当Python中发生异常时,局部变量到底发生了什么?它们是不是被保存在某个地方,以至于没有像正常情况那样超出作用域?控制这种行为的“正确方法”是什么?
3 个回答
一个函数的作用范围是整个函数内部。你可以在finally
块中处理这个问题。
根据这个回答,在处理异常时,可以通过检查异常追踪中的局部变量,方法是使用tb_frame.f_locals
。所以看起来在处理异常的过程中,这些对象是会一直“活着”的。
你的异常处理可能因为保持对帧的引用而导致引用循环。正如文档中所说:
注意 保持对帧对象的引用,比如在这些函数返回的帧记录的第一个元素中,可能会导致你的程序产生引用循环。一旦形成了引用循环,从形成循环的对象可以访问的所有对象的生命周期可能会变得很长,即使Python的可选循环检测器是开启的。如果必须创建这样的循环,确保它们被明确打破是很重要的,以避免对象的延迟销毁和增加的内存消耗。虽然循环检测器会捕捉到这些,但通过在
finally
语句中移除循环,可以让帧(和局部变量)的销毁变得可预测。如果在编译Python时禁用了循环检测器,或者使用了gc.disable()
,这点也很重要。例如:
def handle_stackframe_without_leak():
frame = inspect.currentframe()
try:
# do something with the frame
finally:
del frame