当 Python 3 中已经存在异常时引发异常

Posted

技术标签:

【中文标题】当 Python 3 中已经存在异常时引发异常【英文标题】:Raising exceptions when an exception is already present in Python 3 【发布时间】:2011-09-10 19:53:00 【问题描述】:

在以下代码中引发第二个异常 (B) 时,我的第一个异常 (A) 会发生什么情况?

class A(Exception): pass
class B(Exception): pass

try:
    try:
        raise A('first')
    finally:
        raise B('second')
except X as c:
    print(c)

如果使用X = A 运行,我会得到:

Traceback(最近一次调用最后一次):
  文件“raising_more_exceptions.py”,第 6 行,在
    提高A('第一')
__main__.A:首先

在处理上述异常的过程中,又出现了一个异常:

回溯(最近一次通话最后):
  文件“raising_more_exceptions.py”,第 8 行,在
    提高 B('第二')
__main__.B:第二个

但如果X = B 我得到:

问题

    我的第一个异常去哪儿了? 为什么只能捕获最外层的异常? 如何剥离最外层的异常并重新引发早期的异常?

更新0

这个问题专门针对 Python 3,因为它的异常处理与 Python 2 完全不同。

【问题讨论】:

答案似乎忽略了这样一个事实,即当没有捕获到异常时,我仍然可以获得完整的回溯。请解释一下? 【参考方案1】:

回答问题3,你可以使用:

raise B('second') from None

这将删除异常A traceback。

Traceback (most recent call last):
  File "raising_more_exceptions.py", line 8, in 
    raise B('second')
__main__.B: second

【讨论】:

谢谢,这解决了我的问题...想从基类方法中的 KeyError 引发自定义异常。 My List 是一个子类属性,子类对象是动态生成的。默认的 KeyError trackback 没有说明错误发生在哪个子类中,所以我想抑制它并使用更多信息引发一个新异常。 “从无中提升 X”允许我这样做。 如果你想保存原始回溯,你可以使用:raise OtherException(...).with_traceback(tb) 如文档docs.python.org/3/library/exceptions.html#BaseException中所述 这不是问题 3 所问的。问题 3 询问如何移除 B 并重新加注 A,而不是如何只加注 B 并压制 A。【参考方案2】:

“导致”异常在您的最后一个异常处理程序中以 c.__context__ 的形式提供。 Python 正在使用这些信息来呈现更有用的回溯。在 Python 2.x 下,原始异常会丢失,这仅适用于 Python 3。

通常,您会使用它来引发一致的异常,同时仍保持原始异常可访问(尽管它从异常处理程序自动发生非常酷,但我不知道!):

try:
    do_something_involving_http()
except (URLError, socket.timeout) as ex:
    raise MyError('Network error') from ex

更多信息(以及您可以做的其他一些非常有用的事情):http://docs.python.org/3.3/library/exceptions.html

【讨论】:

【参考方案3】:

Python 的异常处理一次只会处理一个异常。但是,异常对象与其他所有对象一样受制于相同的变量规则和垃圾收集。因此,如果您将异常对象保存在某个变量中,您可以稍后再处理它,即使引发了另一个异常。

在您的情况下,当在“finally”语句期间引发异常时,Python 3 将在第二个异常之前打印出第一个异常的回溯,以提供更多帮助。

更常见的情况是您希望在显式异常处理期间引发异常。然后您可以在下一个异常中“保存”异常。只需将其作为参数传入即可:

>>> class A(Exception):
...     pass
... 
>>> class B(Exception):
...     pass
... 
>>> try:
...     try:
...         raise A('first')
...     except A as e:
...         raise B('second', e)
... except Exception as c:
...     print(c.args[1])
... 
first

如您所见,您现在可以访问原始异常。

【讨论】:

@Matt:它不会去任何地方。不过,我确实意识到我脑子有问题并更新了我的答案。 如何在 doctests 中处理这种情况? 或者可以使用raise x from y 如果你只支持 Python 3,可以。现在这是合理的,但不是在 2011 年。【参考方案4】:

我相信回答您问题的所有要素都已包含在现有答案中。让我结合并详细说明。

让我重复您的问题代码以提供行号参考:

 1  class A(Exception): pass
 2  class B(Exception): pass
 3 
 4  try:
 5      try:
 6          raise A('first')
 7      finally:
 8          raise B('second')
 9  except X as c:
10      print(c)

所以回答你的问题:

    我的第一个异常去哪儿了?

您的第一个异常 A 在第 6 行引发。第 7 行中的 finally 子句总是try 块(第 5-6 行)离开后立即执行,无论是因为成功完成还是因为引发异常而留下。 在执行 finally 子句时,第 8 行引发了另一个异常 B。正如 Lennart 和 Ignazio 所指出的,只有一个例外,即最近提出的例外,可以被跟踪。因此,一旦引发B,整个try 块(第4-8 行)就会退出,如果匹配,则第9 行中的except 语句将捕获异常B(如果XB)。

    为什么只能捕获最外层的异常?

希望现在从我对 1 的解释中可以清楚地看到这一点。不过,您可以捕获内部/较低/第一个异常。要合并 Lennart 的答案,稍作修改,以下是如何同时捕获两者:

class A(Exception): pass
class B(Exception): pass
try:
    try:
        raise A('first')
    except A as e:
        raise B('second', e)
except Exception as c:
    print(c)

输出是:

('second', A('first',))
    如何剥离最外层的异常并重新引发早期的异常?

在 Lennart 的示例中,该问题的解决方案是行 except A as e,其中捕获了内部/下部/第一个异常并将其存储在变量 e 中。

对于何时捕获异常、何时忽略它们以及何时重新引发异常的一般直觉,也许是this question and Alex Martelli's answer 帮助。

【讨论】:

【参考方案5】:
    它被扔掉了。 每个线程一次只能“激活”一个异常。 你不能,除非你以某种方式将前面的异常封装在后面的异常中。

【讨论】:

如果它被抛出,为什么我在没有捕获到异常的情况下得到完整的回溯? 因为你用来运行脚本的工具已经安装了一个全局异常处理程序,并且它注意到了双重异常。 我现在明白了,Python 3.x 有不同的行为。是解释器自己在捕捉它。在 2.x 中,第一个异常被静默删除。

以上是关于当 Python 3 中已经存在异常时引发异常的主要内容,如果未能解决你的问题,请参考以下文章

python 异常处理

当文件存在且无法删除时如何测试方法异常?

小程序中存在构造函数会引发异常

当其中一个线程中存在未捕获的异常时,Python 多线程程序不会退出

Python IOError错误异常原因有哪些?

“ObjectStateManager 中已存在具有相同键的对象...”将实体状态设置为已修改时引发异常