【问题标题】:python how to re-raise an exception which is already caught?python如何重新引发已经捕获的异常?
【发布时间】:2018-12-14 07:38:21
【问题描述】:
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 多个工人,一些工人的逻辑非常复杂,他们可能同时处理多种类型的异常。所以我的问题是我可以只从大师那里提出这些例外而不是编辑作品吗?

【问题讨论】:

  • 我不认为这是一个重复的问题。这里的 OP 清楚地知道如何使用raise,并且该问题的答案中没有任何内容可以解释如何引发已经处理和解除的异常。

标签: python exception


【解决方案1】:

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

如果您不能自己编辑辅助函数,我认为您无能为力。如果some of the solutions from this answer 可以在代码中和控制台上工作,您或许可以使用它们。

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

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

try: 
    # some code
except:
    # some response
    raise

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

except Exception as e:
    # some code
    raise e

【讨论】:

    【解决方案2】:

    您尝试执行的操作将行不通。一旦你处理了一个异常(不重新引发它),异常和伴随的状态就会被清除,所以没有办法访问它。如果您希望异常保持活动状态,则必须要么不处理它,要么手动保持活动状态。

    这在文档中并不容易找到(关于 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 对其进行解析、转换和重新编译,或者直接深入到字节码中) ,但这对于任何类型的生产代码来说几乎都不是一个好主意。

    【讨论】:

      【解决方案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 个,有些工人可能有非常复杂的逻辑二处理多个异常。所以,我的问题是,有什么方法可以控制母版中的所有东西,而不是一个一个地编辑作品?
      • 这就是我提到范围的原因。您甚至可以使用上述将变量分配给引发的实际异常的方法来拥有 {worker:exception} 的字典。
      • 我在答案中添加了一个示例
      猜你喜欢
      • 2011-09-12
      • 2020-10-26
      • 2014-07-09
      • 1970-01-01
      • 2011-09-10
      • 1970-01-01
      • 2011-12-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多