处理sys.exc_info()返回的回溯对象时的正确注意事项和安全性
我知道 sys.exc_info 的文档提到在处理 traceback 对象时要小心,但我还是不太确定某些情况到底安全还是不安全。此外,文档里说“警告:不要这样做!”,紧接着又说“注意:其实可以”,这让我更加困惑。
无论如何,文档和为什么需要显式删除 sys.exc_info() 的 traceback(Alex Martelli 的回答)似乎暗示,只有那些引用了 traceback 值的局部变量才会引发问题。
这让我有几个问题:
- 在这个上下文中,“局部变量”到底是什么意思?我对术语有点困惑,但这是否意味着只有在函数内部创建的变量,还是包括函数参数创建的变量?那其他作用域内的变量,比如全局变量或 self 呢?
- 闭包如何影响 traceback 的潜在循环引用?一般的想法是:闭包可以引用它所包围的函数能引用的所有东西,所以一个引用了闭包的 traceback 可能会引用很多东西。我在想一个更具体的例子,但可能是某种组合:一个内部函数,返回 sys.exc_info() 的代码,以及某处有短命但开销大的对象。
如果我得出的结论或假设有错,请随时告诉我,因为在写这个的过程中,我已经多次对自己的说法产生了信念和怀疑 :)。
虽然我希望能得到我具体例子的答案,但我也在寻求一些关于如何在更复杂的情况下安全处理 traceback 的一般建议、知识或经验分享(例如,你需要运行一个循环并想要收集任何抛出的异常,你需要启动一个新线程并报告它抛出的异常,你需要创建闭包和回调并需要传递抛出的异常等等)。
例子 1:一个处理错误的内部函数
def DoWebRequest():
thread, error_queue = CreateThread(ErrorRaisingFunc)
thread.start()
thread.join()
if not error_queue.empty():
# Purposefully not calling error_queue.get() for illustrative purposes
print 'error!'
def CreateThread(func):
error_queue = Queue.Queue()
def Handled():
try:
func()
except Exception:
error_queue.put(sys.exc_info())
thread = threading.Thread(target=Handled)
return thread, error_queue
这个 Handled()
闭包会导致任何抛出的异常引用 error_queue
吗?这样会导致循环引用,因为 error_queue
也包含 traceback 吗?从 error_queue
中移除 traceback(也就是调用 .get()
)是否足以消除循环引用?
例子 2:在 exc_info 范围内的长生命周期对象,或者返回 exc_info
long_lived_cache = {}
def Alpha(key):
expensive_object = long_lived_cache.get(key)
if not expensive_object:
expensive_object = ComputeExpensiveObject()
long_lived_cache[key] = expensive_object
exc_info = AlphaSub(expensive_object)
if exc_info:
print 'error!', exc_info
def AlphaSub(expensive_object):
try:
ErrorRaisingFunc(expensive_object)
return None
except Exception:
return sys.exc_info()
抛出的异常 AlphaSub()
是否引用了 expensive_object
,而且因为 expensive_object
被缓存,traceback 永远不会消失?如果是这样,怎么打破这样的循环?
另外,exc_info
包含 Alpha
的栈帧,而 Alpha
的栈帧又引用了 exc_info
,导致循环引用。如果是这样,怎么打破这样的循环?
1 个回答
在这个上下文中,“局部变量”到底是什么意思呢?我对术语有点困惑:这是否意味着只有在函数内部创建的变量,还是包括函数参数创建的变量?那其他范围内的变量,比如全局变量或者self呢?
“局部变量”是指在函数内部创建的所有变量。这包括任何函数参数和任何被赋值的变量。例如,在下面的代码中:
def func(fruwappah, qitzy=None):
if fruwappah:
fruit_cake = 'plain'
else:
fruit_cake = qitzy
frosting = 'orange'
变量 fruwappah
、qitzy
、fruit_cake
和 frosting
都是局部变量。哦,还有,因为 self
在函数头里(虽然在我的例子中没有),它也是局部的。
闭包是如何影响追踪回溯中的潜在循环引用的?一般的想法是:闭包可以引用它所包含的函数的所有内容,因此带有闭包引用的追踪回溯可能会引用很多东西。我在想一个更具体的例子,但可能会涉及到:一个内部函数、返回 sys.exc_info() 的代码,以及某个地方的短暂且开销大的对象。
正如你链接的答案所说:追踪回溯会引用在异常发生时所有活跃的函数(及其变量)。换句话说,是否涉及闭包并不重要——给闭包(非局部)或全局变量赋值都会产生循环引用。
处理这个问题有两种基本方法:
- 定义一个在异常被抛出后会被调用的函数——它在追踪回溯中不会有栈帧,所以当它结束时,所有变量,包括追踪回溯,都会消失;或者
- 确保在完成后使用
del traceback_object
来删除它。
说了这些,我在自己的代码中还没有需要追踪回溯对象的情况——异常及其各种属性到目前为止已经足够了。