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

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

提问人:Tiancheng Liu 提问时间:7/6/2018 最后编辑:Tiancheng Liu 更新时间:7/6/2018 访问量:26375

问:

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() 将所有 None 返回给 3 个元素,如何在 master 函数中重新引发异常? 我正在使用 Python 2.7

一次更新: 我有 1000 多个 worker,有些 worker 的逻辑非常复杂,他们可能会同时处理多种类型的异常。所以我的问题是,我可以只从master而不是edit works中提出这些异常吗?

Python 异常

评论


答:

3赞 krflol 7/6/2018 #1

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

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

评论

0赞 Tiancheng Liu 7/6/2018
我知道这个会起作用。但就像我说的,我有一堆作品,有1000多件,有些工人可能有非常复杂的逻辑,二处理多个异常。所以,我的问题是,有没有办法从母版控制所有的东西,而不是一个接一个地编辑作品?
0赞 krflol 7/6/2018
这就是我提到范围的原因。您甚至可以使用上述方法将变量分配给引发的实际异常,从而获得 {worker:exception} 的字典。
0赞 krflol 7/6/2018
我在答案中添加了一个示例
5赞 abarnert 7/6/2018 #2

你试图做的事情是行不通的。处理异常(不重新引发异常)后,该异常和伴随的状态将被清除,因此无法访问它。如果希望异常保持活动状态,则必须不处理它,或者手动使其保持活动状态。

这在文档中并不容易找到(关于 CPython 的底层实现细节要容易一些,但理想情况下,我们想知道该语言定义了什么 Python),但它就在那里,埋在 except 参考中:

...这意味着必须将异常分配给不同的名称,以便能够在 except 子句之后引用它。异常被清除,因为附加回溯后,它们会与堆栈帧形成一个引用循环,使该帧中的所有局部变量保持活动状态,直到下一次垃圾回收发生。

在执行 except 子句的套件之前,有关异常的详细信息存储在模块中,可以通过 访问。 返回一个 3 元组,该元组由异常类、异常实例和回溯对象组成(请参阅标准类型层次结构部分),用于标识程序中发生异常的点。 当从处理异常的函数返回时,值将还原到其以前的值(在调用之前)。syssys.exc_info()sys.exc_info()sys.exc_info()

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


因此,最简单的方法是将工作线程更改为不处理异常(或记录然后重新引发异常,或其他任何方式),并让异常处理按预期方式工作。

在某些情况下,您无法执行此操作。例如,如果实际代码在后台线程中运行工作线程,则调用方不会看到异常。在这种情况下,您需要手动将其传回。举个简单的例子,让我们更改工作函数的 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.futuresFuture


如果你不能修改工人,你基本上就不走运了。当然,你可以编写一些可怕的代码来在运行时修补工作线程(通过使用获取它们的源代码,然后用于解析、转换和重新编译它,或者直接潜入字节码),但对于任何类型的生产代码来说,这几乎都不是一个好主意。inspectast

24赞 Green Cloak Guy 7/6/2018 #3

在您的情况下,异常返回 .一旦发生这种情况,就无法恢复异常。如果您的主函数知道每个函数的返回值应该是多少(例如,在 reutrns 中,您可以手动重新引发异常。workerNoneZeroDivisionErrorworkerNone

如果您无法编辑工作函数本身,我认为您能做的不多。如果这些解决方案在代码和控制台上都有效,则可以使用此答案中的某些解决方案

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

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

try: 
    # some code
except:
    # some response
    raise

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

except Exception as e:
    # some code
    raise e