【问题标题】:Python function local variable scope during exceptions异常期间的 Python 函数局部变量范围
【发布时间】:2010-01-13 09:30:22
【问题描述】:

背景:我正在用 Python 对 National Instruments 的 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 因异常被删除较晚。

如果我取消注释将parameterlocal_variable 设置为None 的行,那么我会得到我所期望的:

Init outer_object
Init inner_object
Del inner_object
Del outer_object
Traceback (most recent call last):
...
Exception: Test exception

那么当 Python 中发生异常时,函数局部变量究竟会发生什么?它们是否被保存在某个地方,因此它们不会像往常一样超出范围?控制这种行为的“正确方法”是什么?

【问题讨论】:

    标签: python exception exception-handling scope


    【解决方案1】:

    您的异常处理可能是通过保持对帧的引用来创建引用循环。正如the docs 所说:

    注意 保留对框架的引用 对象,如在第一个元素中找到的 框架记录了这些功能 return [[注意:这里的“这些功能”是指 一些在模块inspect,但其余的 段落适用范围更广!]],可能会导致您的程序 创建参考循环。一旦 参考循环已创建, 所有物体的寿命 从形成的对象访问 周期甚至可以变得更长 如果 Python 的可选循环检测器是 启用。如果这样的周期必须 创建,重要的是要确保 它们被明确打破以避免 对象的延迟销毁和 增加内存消耗 发生。虽然周期检测器会 抓住这些,破坏框架 (和局部变量)可以制作 通过删除循环确定性 finally 子句。这也是 如果循环检测器是重要的 编译 Python 时禁用或 使用gc.disable()。例如:

    def handle_stackframe_without_leak():
        frame = inspect.currentframe()
        try:
            # do something with the frame
        finally:
            del frame
    

    【讨论】:

    • 谢谢。我不太确定我是否理解“您的异常处理可能是通过保留对帧的引用来创建引用循环。”所以我添加了一个代码示例。我很想知道您对此有何看法。
    • @Craig,没有引用循环,但仍然存在比您可能想要的更长的引用,因为堆栈中的所有帧(以及它们对对象的引用)必须在异常存在时存活处理(当您将所有这些引用设置为 None 时,即使框架仍然存在,对象也可以被删除)。
    【解决方案2】:

    函数的作用域是整个函数。在finally 中处理此问题。

    【讨论】:

      【解决方案3】:

      根据this answer for another question,可以通过tb_frame.f_localsinspect local variables on the frame in an exception traceback。所以看起来对象在异常处理期间保持“活动”。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-08-09
        • 1970-01-01
        • 2014-07-14
        • 2013-07-13
        • 1970-01-01
        • 1970-01-01
        • 2016-11-13
        • 2013-10-14
        相关资源
        最近更新 更多