如果函数引发错误,则显式删除函数内的变量

问题描述 投票:0回答:1

单元测试的一个特殊问题。我的许多测试函数都具有以下结构:

def test_xxx():
    try:
        # do-something
        variable1 = ...
        variable2 = ...
    except Exception as error:
        raise error
    finally:
        try:
            del variable1
        except Exception:
            pass
        try:
            del variable2
        except Exception:
            pass

而且这个结构显然不是很好。我可以用以下方式简化

finally
语句:

finally:
    for variable in ("variable1", "variable2"):
        if variable in locals():
            del locals()[variable]

但它仍然不是那么好。相反,我想使用装饰器

del_variables
来处理
try
/
except
/
finally

尝试,不起作用:

def del_variables(*variables):
    def decorator(function):
        def wrapper(*args, **kwargs):
            try:
                return function(*args, **kwargs)
            except Exception as error:
                raise error
            finally:
                for variable in variables:
                    if variable in locals():
                        del locals()[variable]
        return wrapper
    return decorator


@del_variables("c")
def foo(a, b):
    c = 3
    return a + b + c

因为

locals()
并不引用
function
执行中的命名空间。如果我使用
vars()
,我不知道应该提供什么参数,因为就 Python 而言,该命名空间已经是 GC 了。知道如何让装饰器工作吗,即如何从
function
的命名空间中显式删除变量?


注意:是的,这是一个奇怪的情况,我需要显式调用

del
,因为我覆盖了某些对象(存储在我想要删除的变量中)的
__del__
方法来调用c++函数来销毁c++对象并附参考文献。不,在这种情况下,pytest 装置并不是一个好的解决方案;)

python namespaces pytest decorator
1个回答
0
投票

你真的应该考虑重构你的代码。一开始就依赖终结器并不是一个好主意(例如参见此问答)。有更好的机制可用于资源清理,例如上下文管理器和/或 pytest 固定装置。

话虽这么说:

如果测试失败,pytest 会保留引发的异常。该异常包含所有堆栈帧。测试函数的堆栈帧包含对测试中创建的所有对象的引用。

使用当前的解决方案,您只需删除测试函数中的引用。然而,可能有更多对其他堆栈帧中的对象的引用。例如:

def func(some_obj):
    raise Exception

def test_test():
    obj = MyObj()
    func(obj)

del obj
不会导致对象被垃圾回收,因为现在
func
的堆栈帧仍然包含对该对象的引用。

由于您无法通过调用堆栈合理地跟踪对对象的引用(它可能包含在其他对象等内部的字典中),因此我认为没有简单的方法可以仅删除您请求的选定对象。但是,假设没有对对象的全局或循环引用,您可以简单地从后续帧中删除所有局部变量。哦,当然,我们只讨论 CPython。

这可以通过多种方式实现,但因为你想要一个装饰器:

import ctypes
import sys

def delete_locals(func):
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        finally:
            tb = sys.exc_info()[2]
            if tb:
                while tb.tb_next:
                    tb = tb.tb_next
                    tb.tb_frame.f_locals.clear()
                    ctypes.pythonapi.PyFrame_LocalsToFast(
                        ctypes.py_object(tb.tb_frame), ctypes.c_int(1)
                    )  # reference: https://stackoverflow.com/a/34671307

    return wrapper

请记住,pytest 现在也没有机会告诉您输出中局部变量的值。

© www.soinside.com 2019 - 2024. All rights reserved.