【问题标题】:How to re-raise an exception in nested try/except blocks?如何在嵌套的 try/except 块中重新引发异常?
【发布时间】:2013-08-13 20:34:39
【问题描述】:

我知道,如果我想重新引发异常,我只需在相应的 except 块中使用不带参数的 raise。但是给定一个像

这样的嵌套表达式
try:
    something()
except SomeError as e:
    try:
        plan_B()
    except AlsoFailsError:
        raise e  # I'd like to raise the SomeError as if plan_B()
                 # didn't raise the AlsoFailsError

如何在不破坏堆栈跟踪的情况下重新提升 SomeError?在这种情况下,仅raise 将重新提出最近的AlsoFailsError。或者我怎样才能重构我的代码来避免这个问题?

【问题讨论】:

  • 您是否尝试将plan_B 放在另一个函数中,该函数在成功时返回True,在异常时返回False?那么外部 except 块可能只是 if not try_plan_B(): raise
  • @DrewMcGowen 不幸的是,更现实的情况是,这是在接受任意对象arg 的函数内部,我会尝试调用arg.plan_B(),这可能会引发AttributeError,因为arg 不是提供B计划
  • @Paco 谢谢,我会的(虽然an answer 已经展示了一种更简单的方法)
  • @DrewMcGowen 我写了an answer based on your comment,虽然它看起来不像user4815162342's answer 那样pythonic。但那是因为我也想有一个返回值并允许plan_B 引发异常

标签: python exception nested raise


【解决方案1】:

即使accepted solution 是正确的,最好使用six.reraise 指向具有Python 2+3 解决方案的Six 库。

六.再融资exc_typeexc_valueexc_traceback=None)

重新引发异常,可能带有不同的回溯。 [...]

所以,你可以写:

import six


try:
    something()
except SomeError:
    t, v, tb = sys.exc_info()
    try:
        plan_B()
    except AlsoFailsError:
        six.reraise(t, v, tb)

【讨论】:

  • 好点 - 说到六,如果你想包含 plan_B() 也失败的信息,你也可以使用 six.raise_from
  • @TobiasKienzler:我认为这是一种不同的用法:使用six.raise_from,您创建了一个与前一个异常相关联的新异常,您无需重新引发,所以追溯是不同的。
  • 我的意思是——如果你reraise你得到的印象只有something()SomeError,如果你raise_from你也知道这导致plan_B()被执行但抛出AlsoFailsError。所以这取决于用例。我认为raise_from 会让调试更容易
【解决方案2】:

从 Python 3 开始,回溯存储在异常中,所以一个简单的 raise e 将做(大部分)正确的事情:

try:
    something()
except SomeError as e:
    try:
        plan_B()
    except AlsoFailsError:
        raise e  # or raise e from None - see below

产生的回溯将包括在处理AlsoFailsError 时发生SomeError 的附加通知(因为raise eexcept AlsoFailsError 内部)。这是一种误导,因为实际发生的情况正好相反——我们遇到了AlsoFailsError,并处理了它,同时试图从SomeError 中恢复。要获取不包含AlsoFailsError 的回溯,请将raise e 替换为raise e from None

在 Python 2 中,您可以将异常类型、值和回溯存储在局部变量中并使用 three-argument form of raise

try:
    something()
except SomeError:
    t, v, tb = sys.exc_info()
    try:
        plan_B()
    except AlsoFailsError:
        raise t, v, tb

【讨论】:

  • 完美,我刚刚也找到了here,谢谢!虽然那里的建议是 raise self.exc_info[1], None, self.exc_info[2]self.exc_info = sys.exc_info() 之后 - 出于某种原因将 [1] 放在首位
  • @TobiasKienzler raise t, None, tb 将丢失异常的值,并将强制 raise 从类型中重新实例化它,从而为您提供不太具体(或根本不正确)的异常值。例如,如果引发的异常是 KeyError("some-key"),它只会重新引发 KeyError() 并从回溯中省略确切的缺失键。
  • @TobiasKienzler 在 Python 3 中应该仍然可以将其表示为 raise v.with_traceback(tb)。 (您的评论甚至说了这么多,只是它建议重新实例化该值。)
  • 此外,在 Python 2.0(13 年前发布)之前,不要将 sys.exc_info() 存储在局部变量中的红色警告是有意义的,但在今天已经荒谬了。如果没有循环收集器,现代 Python 将几乎毫无用处,因为每个重要的 Python 库都会不间断地创建循环并依赖于它们的正确清理。
  • @user4815162342 您可以通过编写“raise e from None”来终止“另一个错误发生”嵌套错误。
【解决方案3】:

Python 3.5+ 无论如何都会将回溯信息附加到错误中,因此不再需要单独保存它。

>>> def f():
...   try:
...     raise SyntaxError
...   except Exception as e:
...     err = e
...     try:
...       raise AttributeError
...     except Exception as e1:
...       raise err from None
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in f
  File "<stdin>", line 3, in f
SyntaxError: None
>>> 

【讨论】:

  • 问题是关于except 期间发生的另一个异常。但你是对的,当我将err = e 替换为raise AttributeError 时,你首先会得到SyntaxError 堆栈跟踪,然后是During handling of the above exception, another exception occurred:AttributeError 堆栈跟踪。很高兴知道,但不幸的是,不能依赖安装 3.5+。 PS: ff verstehen nicht-Deutsche vermutlich nicht ;)
  • 好的,所以我更改了示例以引发另一个异常,当我重新引发第一个异常时,该异常(正如最初的问题所要求的)被忽略。
  • @TobiasKienzler 3.5+(我将其更改为)似乎是一种全球认可的格式。是denkst du吗? ;)
  • @linusg 同意 :)
【解决方案4】:

根据Drew McGowen's suggestion,但考虑到一般情况(返回值s 存在),这里是user4815162342's answer 的替代方案:

try:
    s = something()
except SomeError as e:
    def wrapped_plan_B():
        try:
            return False, plan_B()
        except:
            return True, None
    failed, s = wrapped_plan_B()
    if failed:
        raise

【讨论】:

  • 这种方法的好处是它在 Python 2 和 3 中保持不变。
  • @user4815162342 好点 :) 虽然同时在 Python3 中我会考虑 raise from,所以堆栈跟踪也会让我看到计划 B 失败。顺便说一下can be emulated in Python 2
猜你喜欢
  • 2023-03-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-05
相关资源
最近更新 更多