Python异常链

100

在Python中,是否有一种标准的异常链使用方式?类似于Java异常的“caused by”?

以下是一些背景信息。

我有一个模块,其中包含一个主要的异常类DSError

 class DSError(Exception):
     pass

在这个模块的某个地方,会有以下内容:

try:
    v = my_dict[k]
    something(v)
except KeyError as e:
    raise DSError("no key %s found for %s" % (k, self))
except ValueError as e:
    raise DSError("Bad Value %s found for %s" % (v, self))
except DSError as e:
    raise DSError("%s raised in %s" % (e, self))

基本上,这段代码应该只会抛出 DSError 并告诉我发生了什么以及为什么会这样。问题在于 try 块可能会抛出很多其他异常,所以我更喜欢能够这样做:

try:
    v = my_dict[k]
    something(v)
except Exception as e:
    raise DSError(self, v, e)  # Exception chained...

这是标准的Python方式吗?我没有在其他模块中看到异常链,那在Python中如何实现呢?


你想要输出什么?我无法确定你是否想要原始异常的堆栈跟踪,还是想要隐藏它,并只使用一个概括原始异常的单个消息来抛出自己的异常? - BrenBarn
原始跟踪会更好,因为try块可能会从模块中递归调用。 - Ayman
2个回答

158

异常链仅在Python 3中可用,您可以编写以下代码:

try:
    v = {}['a']
except KeyError as e:
    raise ValueError('failed') from e

生成如下输出结果:

Traceback (most recent call last):
  File "t.py", line 2, in <module>
    v = {}['a']
KeyError: 'a'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "t.py", line 4, in <module>
    raise ValueError('failed') from e
ValueError: failed

在大多数情况下,您甚至不需要使用from。Python 3默认会显示在异常处理期间发生的所有异常,如下所示:

Traceback (most recent call last):
  File "t.py", line 2, in <module>
    v = {}['a']
KeyError: 'a'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "t.py", line 4, in <module>
    raise ValueError('failed')
ValueError: failed

Python 2 中,你可以给你的异常类添加自定义属性,例如:

class MyError(Exception):
    def __init__(self, message, cause):
        super(MyError, self).__init__(message + u', caused by ' + repr(cause))
        self.cause = cause

try:
    v = {}['a']
except KeyError as e:
    raise MyError('failed', e)

2
对于Python 2,如果想要保存追踪信息(这是必须的),可以使用以下代码:raise MyError(message + u', caused by ' + repr(cause)), None, sys.exc_info()[2] - Mr_and_Mrs_D
3
在大多数情况下,你甚至不需要“from”这个词。你有需要或有用的例子吗? - timgeb
3
@timgeb PEP 3134 定义了两种链式异常的情况:一种是错误处理代码导致抛出另一个异常,另一种是将异常有意地转换为不同的异常。from e 是用于后一种情况的,并且会更改输出中的消息,就像上面的答案所示。 - Eric Smith
2
在我看来,这个回答比重复问题的选定答案更好,因为它涵盖了Python 3。此外,感谢您指出from甚至不是必要的,因为Python 3的跟踪已经打印出堆栈中所有当前异常。 - Alvaro Gutierrez Perez
点个赞!如果您也想从这里返回一个值,您会怎么做? - PirateApp
显示剩余3条评论

5

您是想要此内容吗?

class MyError(Exception):
    def __init__(self, other):
        super(MyError, self).__init__(other.message)

>>> try:
...     1/0
... except Exception, e:
...     raise MyError(e)
Traceback (most recent call last):
  File "<pyshell#27>", line 4, in <module>
    raise MyError(e)
MyError: division by zero

如果您想存储原始异常对象,可以在自己的异常类的__init__中这样做。实际上,您可能希望将回溯存储为异常对象本身并且不提供有关异常发生位置的有用信息:
class MyError(Exception):
    def __init__(self, other):
        self.traceback = sys.exc_info()
        super(MyError, self).__init__(other.message)

接下来,您可以访问异常的traceback属性,以获取有关原始异常的信息。(Python 3已将此作为异常对象的__traceback__属性提供。)


几乎正确,但我认为这将是“作弊”,因为它只获取链接异常的消息,而不是实际异常对象。也就是说,我不知道实际的除零发生在哪里,只知道它被捕获在某个地方。 - Ayman
@Ayman: 看一下我的修改过的答案。你所需要做的就是获取traceback并存储它。然而,如果你真的想要所有原始异常中的信息像真正的异常一样显示在traceback中,那么phihag是正确的,在Python 2中无法实现。你只需手动将旧的traceback作为异常消息的一部分打印即可。 - BrenBarn
谢谢。我不知道sys.exc_info()。我也会接受这个作为答案 :-) - Ayman
1
other.message 是否总是存在? - Mr_and_Mrs_D

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接