你试图做的事情行不通。一旦处理了异常(而没有重新引发它),异常及其相关状态就被清除了,因此无法访问它。如果你想让异常保持活动状态,要么不处理它,要么手动保持它的活动状态。
这在文档中并不容易找到(在CPython的底层实现细节方面更容易一些,但理想情况下我们想知道Python语言定义的内容),但它存在于
except
参考文献中。
这意味着异常必须被分配给不同的名称,才能在except子句之后引用它。异常被清除,因为它们与堆栈帧形成引用循环,并附有跟踪信息,保持该帧中的所有局部变量存活,直到下一次垃圾回收发生。
在执行except子句的套件之前,有关异常的详细信息存储在sys模块中,并且可以通过sys.exc_info()访问。sys.exc_info()返回一个3元组,其中包含异常类、异常实例和一个跟踪对象(请参阅标准类型层次结构),用于标识程序中发生异常的点。从处理异常的函数返回时,sys.exc_info()值将恢复为调用之前的值。
此外,这确实是异常处理程序的重点:当函数处理异常时,对于函数外部的世界来说,似乎没有异常发生。这在Python中比许多其他语言更为重要,因为Python如此随意地使用异常——每个for循环、每个hasattr调用等都会引发和处理异常,而您不想看到它们。
所以,最简单的方法是将工作程序员更改为不处理异常(或日志记录并重新引发异常等),让异常处理按照它应有的方式工作。
有一些情况下你无法这样做。例如,如果您的实际代码在后台线程中运行工作程序员,则调用者将看不到异常。在这种情况下,您需要手动传递回来。举个简单的例子,让我们更改您的工作函数API,使其返回一个值和异常:
def worker(a):
try:
return 1 / a, None
except ZeroDivisionError as e:
return None, e
def master():
res, e = worker(0)
if e:
print(e)
raise e
很明显,您可以进一步扩展它来返回整个exc_info
三元组或其他任何您想要的内容;我只是为了示例尽可能地保持简单。
如果您查看像concurrent.futures
这样的东西的内部结构,这就是它们处理从在线程或进程池上运行的任务传递异常回父级的方式(例如,当您等待一个Future
时)。
如果你不能修改工作者,那么你基本上就没有什么运气。当然,你可以编写一些可怕的代码来在运行时修补工作者(通过使用
inspect
获取它们的源代码,然后使用
ast
进行解析、转换和重新编译,或者直接深入到字节码中),但这几乎永远不会是任何生产代码的好主意。