Python如何重新引发已经捕获的异常?

15
import sys
def worker(a):
    try:
        return 1 / a
    except ZeroDivisionError:
        return None


def master():
    res = worker(0)
    if not res:
        print(sys.exc_info())
        raise sys.exc_info()[0]

如下所示的代码片段中,我有一堆类似于worker的函数。它们已经拥有自己的try-except块来处理异常。然后一个主函数将调用每个worker。现在,sys.exc_info()返回所有None到3个元素,请问如何在主函数中重新引发异常? 我正在使用Python 2.7

更新一下: 我有超过1000个工作程序,有些工作程序具有非常复杂的逻辑,它们可能同时处理多种类型的异常。所以我的问题是,我是否可以仅从主函数中引发这些异常而不编辑函数?

3个回答

24

在你的情况下,worker 中的异常会返回 None。一旦发生这种情况,就无法再获取异常了。如果您的主函数知道每个函数应该返回什么值(例如,在 worker 中,ZeroDivisionError 返回 None),则可以手动重新引发异常。

如果你不能编辑工作函数本身,我认为你没有太多办法。您可能可以使用此答案中的一些解决方案,如果它们在代码中也能起作用。

krflol 的代码有点像 C 如何处理异常 - 当异常发生时,会分配一个数字给一个全局变量,稍后可以交叉引用以找出异常是什么。那也是一个可能的解决方案。

如果您愿意编辑工人函数,那么将异常升级到调用该函数的代码实际上非常简单:

try: 
    # some code
except:
    # some response
    raise

如果在catch块的结尾使用一个空白的raise,它将重新引发刚刚捕获的相同异常。或者,如果需要调试打印,您可以为异常命名并执行相同的操作,甚至引发另一个异常。

except Exception as e:
    # some code
    raise e

5
你试图做的事情行不通。一旦处理了异常(而没有重新引发它),异常及其相关状态就被清除了,因此无法访问它。如果你想让异常保持活动状态,要么不处理它,要么手动保持它的活动状态。
这在文档中并不容易找到(在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进行解析、转换和重新编译,或者直接深入到字节码中),但这几乎永远不会是任何生产代码的好主意。

3

虽然未经测试,但我怀疑你可以这样做。根据变量的范围,您可能需要进行更改,但我认为您会理解这个想法。

try:
    something
except Exception as e:
    variable_to_make_exception = e

稍后使用变量的示例。

使用这种处理错误的方法的示例:

errors = {}
try:
    print(foo)
except Exception as e:
    errors['foo'] = e
try:
    print(bar)
except Exception as e:
    errors['bar'] = e


print(errors)
raise errors['foo']

输出..

{'foo': NameError("name 'foo' is not defined",), 'bar': NameError("name 'bar' is not defined",)}
Traceback (most recent call last):
  File "<input>", line 13, in <module>
  File "<input>", line 3, in <module>
NameError: name 'foo' is not defined

我知道这个方法会起作用。但是像我说的,我有很多工作要做,超过1000个,而且一些工人可能需要处理多个异常的复杂逻辑。 所以,我的问题是,是否有办法从主控制所有的东西,而不是逐个编辑每个工作? - Tiancheng Liu
这就是我提到作用域的原因。你甚至可以使用上述方法将变量分配给实际引发的异常,从而拥有一个{worker:exception}字典。 - krflol
我在我的回答中添加了一个例子。 - krflol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接