python如何重新引发已经捕获的异常? [英] python how to re-raise an exception which is already caught?

查看:180
本文介绍了python如何重新引发已经捕获的异常?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

  import sys 
def worker(a):
try:
return 1 / a
除了ZeroDivisionError:
return无


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

如上面的代码所示,我有一堆功能像工人。他们已经有自己的try-except块来处理异常。然后,一个主函数将调用每个工作者。现在,sys.exc_info()将所有None返回3个元素,如何在master函数中重新引发异常?
我正在使用Python 2.7



一个更新:
我有1000多名工人,有些工人的逻辑很复杂,他们可能处理多种类型同时出现异常。所以我的问题是,我可以只提出来自master的那些例外而不是编辑作品吗?

解决方案

您试图做的事情赢得了不行处理完异常(不重新引发)后,该异常及其伴随的状态将被清除,因此无法访问它。如果要让异常保持生命,就必须要么不处理它,要么手动使其保持生命。



在文档中查找起来并不容易(有关CPython的基础实现细节要容易一些,但理想情况下,我们想知道该语言定义的Python),但是它已经埋在 除外 参考:


…这意味着必须将异常分配给其他名称,以便能够在except子句之后引用该异常。清除异常是因为它们具有附加的回溯,它们与堆栈框架形成了一个参考循环,使该框架中的所有本地对象都保持活动状态,直到发生下一个垃圾回收为止。



在执行except子句的套件之前,有关异常的详细信息存储在 sys 模块中,可以通过 sys.exc_info() sys.exc_info()返回一个三元组,该三元组由异常类,异常实例和回溯对象组成(请参见标准类型层次结构一节),用于标识程序中的点发生异常的地方。从处理异常的函数返回时, sys.exc_info()的值恢复为先前的值(在调用之前)。


此外,这实际上是异常处理程序的要点:当一个函数处理异常时,对于该函数之外的世界来说,似乎没有异常发生。这在Python中比在许多其他语言中更为重要,因为Python如此混杂地使用异常-每个 for 循环,每个 hasattr 呼叫等正在引发并处理异常,而您不希望看到它们。






所以,执行此操作的最简单方法是只更改工作程序以不处理异常(或先记录日志然后重新引发它们,或进行其他操作),然后让异常处理按其应有的方式工作。



在某些情况下您无法执行此操作。例如,如果您的实际代码在后台线程中运行工作线程,则调用者将看不到异常。在这种情况下,您需要手动将其传递回去。举一个简单的例子,让我们更改工作函数的API以返回值和异常:

  def worker(a) :
尝试:
返回1 / a,无
除ZeroDivisionError外为e:
返回None,e

def master():
res,e = worker(0)
如果e:
print(e)
加薪e

显然,您可以将其扩展得更远,以返回整个 exc_info 三元组,或者您想要的其他任何内容;对于示例,我只是将其保持尽可能简单。



如果您查看诸如 concurrent.futures ,这是它们处理从线程或进程池上运行的任务的异常传递回父级的方式(例如,当您等待 Future 时)。 / p>




如果您无法修改工人,那么您基本上就没有运气了。当然,您可以编写一些可怕的代码在运行时修补工作程序(通过使用 inspect 获取工作人员的源,然后使用 ast 对其进行解析,转换和重新编译,或直接向下钻入字节码),但这对于任何一种生产代码来说几乎都不是一个好主意。


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]

As code piece above, I have a bunch of functions like worker. They already have their own try-except block to handle exceptions. And then one master function will call each worker. Right now, sys.exc_info() return all None to 3 elements, how to re-raise the exceptions in the master function? I am using Python 2.7

One update: I have more than 1000 workers and some worker has very complex logic, they may deal multiple types of exceptions at same time. So my question is can I just raise those exceptions from master rather than edit works?

解决方案

What you're trying to do won't work. Once you handle an exception (without re-raising it), the exception, and the accompanying state, is cleared, so there's no way to access it. If you want the exception to stay alive, you have to either not handle it, or keep it alive manually.

This isn't that easy to find in the docs (the underlying implementation details about CPython are a bit easier, but ideally we want to know what Python the language defines), but it's there, buried in the except reference:

… 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.

Also, this is really the point of exception handlers: when a function handles an exception, to the world outside that function, it looks like no exception happened. This is even more important in Python than in many other languages, because Python uses exceptions so promiscuously—every for loop, every hasattr call, etc. is raising and handling an exception, and you don't want to see them.


So, the simplest way to do this is to just change the workers to not handle the exceptions (or to log and then re-raise them, or whatever), and let exception handling work the way it's meant to.

There are a few cases where you can't do this. For example, if your actual code is running the workers in background threads, the caller won't see the exception. In that case, you need to pass it back manually. For a simple example, let's change the API of your worker functions to return a value and an exception:

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

Obviously you can extend this farther to return the whole exc_info triple, or whatever else you want; I'm just keeping this as simple as possible for the example.

If you look inside the covers of things like concurrent.futures, this is how they handle passing exceptions from tasks running on a thread or process pool back to the parent (e.g., when you wait on a Future).


If you can't modify the workers, you're basically out of luck. Sure, you could write some horrible code to patch the workers at runtime (by using inspect to get their source and then using ast to parse, transform, and re-compile it, or by diving right down into the bytecode), but this is almost never going to be a good idea for any kind of production code.

这篇关于python如何重新引发已经捕获的异常?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆