当 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
(如果X
是B
)。
-
为什么只能捕获最外层的异常?
希望现在从我对 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 中已经存在异常时引发异常的主要内容,如果未能解决你的问题,请参考以下文章