您尝试执行的操作将行不通。一旦你处理了一个异常(不重新引发它),异常和伴随的状态就会被清除,所以没有办法访问它。如果您希望异常保持活动状态,则必须要么不处理它,要么手动保持活动状态。
这在文档中并不容易找到(关于 CPython 的底层实现细节要容易一些,但理想情况下我们想知道该语言定义了什么 Python),但它就在那里,埋在 except 参考资料中:
... 这意味着必须将异常分配给不同的名称,以便能够在 except 子句之后引用它。异常被清除是因为附加了回溯,它们与堆栈帧形成一个引用循环,使该帧中的所有本地人保持活动状态,直到下一次垃圾回收发生。
在执行 except 子句套件之前,有关异常的详细信息存储在 sys 模块中,可以通过 sys.exc_info() 访问。 sys.exc_info() 返回一个由异常类、异常实例和回溯对象组成的三元组(参见标准类型层次结构部分),用于标识程序中发生异常的点。当从处理异常的函数返回时,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 对其进行解析、转换和重新编译,或者直接深入到字节码中) ,但这对于任何类型的生产代码来说几乎都不是一个好主意。