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

python how to re-raise an exception which is already caught?

1
2
3
4
5
6
7
8
9
10
11
12
13
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块来处理异常。然后一个主函数将调用每个工人。现在,sys.exc_info()返回全部无到3个元素,如何在master函数中重新引发异常?我使用的是python 2.7

一个更新:我有1000多个工人,有些工人的逻辑非常复杂,他们可以同时处理多种类型的异常。所以我的问题是,我能不能从大师那里提出这些例外,而不是编辑作品?


你想做的事行不通。一旦您处理了一个异常(不重新引发它),该异常以及伴随的状态将被清除,因此无法访问它。如果您希望异常保持活动状态,则必须要么不处理它,要么手动保持它的活动状态。

这不是很容易在文档中找到的(关于cpython的底层实现细节有点简单,但理想情况下我们想知道语言定义了什么python),但它就在那里,隐藏在except引用中:

… This means the exception must be assigned to a different name to be able to refer to it after the except clause. Exceptions are cleared because with the traceback attached to them, they form a reference cycle with the stack frame, keeping all locals in that frame alive until the next garbage collection occurs.

Before an except clause’s suite is executed, details about the exception are stored in the sys module and can be accessed via sys.exc_info(). sys.exc_info() returns a 3-tuple consisting of the exception class, the exception instance and a traceback object (see section The standard type hierarchy) identifying the point in the program where the exception occurred. sys.exc_info() values are restored to their previous values (before the call) when returning from a function that handled an exception.

此外,这确实是异常处理程序的要点:当一个函数处理一个异常时,对于该函数之外的世界,它看起来没有发生异常。这在python中比在其他许多语言中更重要,因为python使用异常非常混乱,每个for循环、每个hasattr调用等都在引发和处理异常,而您不想看到它们。

所以,最简单的方法就是改变工作人员不处理异常(或者记录然后重新引发异常,或者其他),并让异常处理按照它的意图工作。

有些情况下你不能这样做。例如,如果您的实际代码正在后台线程中运行工作线程,则调用方将看不到异常。在这种情况下,您需要手动将其传回。对于一个简单的示例,让我们更改工作函数的API以返回值和异常:

1
2
3
4
5
6
7
8
9
10
11
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解析、转换和重新编译它,或者直接插入到字节码中),但对于任何类型的生产代码来说,这几乎都不是一个好主意。


没有测试过,但我怀疑你可以这样做。根据变量的范围,你必须改变它,但我想你会明白的

1
2
3
4
try:
    something
except Exception as e:
    variable_to_make_exception = e

…..稍后使用变量

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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']

输出…

1
2
3
4
5
{'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


在您的情况下,worker中的异常返回None。一旦发生这种情况,就不会有任何例外。如果主函数知道每个函数的返回值应该是什么(例如,worker中的ZeroDivisionError,则可以手动重新发出异常。

如果你不能自己编辑工人的功能,我认为你做的不多。如果这些解决方案既能在代码中工作,也能在控制台上工作,那么您可能能够使用这个答案中的一些解决方案。

上面的krflol代码有点像C处理异常的方式——有一个全局变量,每当发生异常时,它都会被分配一个数字,稍后可以交叉引用该数字来确定异常是什么。这也是一个可能的解决方案。

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

1
2
3
4
5
try:
    # some code
except:
    # some response
    raise

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

1
2
3
except Exception as e:
    # some code
    raise e