使用不同的类型和消息重新引发异常,保留现有信息

我正在编写一个模块,希望对它可能引发的异常有一个统一的异常层次结构(例如,对于所有 foo模块的特定异常,从 FooError抽象类继承)。这允许模块的用户捕获这些特定的异常,并在需要时清楚地处理它们。但是从模块引发的许多异常都是由于其他异常引发的; 例如,在某个任务中由于文件上的 OSERerror 而失败。

我需要的是 “包装”捕获的异常,使其具有不同的类型和消息,这样信息就可以通过任何捕获异常的方式在传播层次结构的更高层次上获得。但是我不想丢失现有的类型、消息和堆栈跟踪; 这些对于尝试调试问题的人来说都是有用的信息。顶级异常处理程序是不好的,因为我试图在异常进一步向上传播堆栈之前修饰它,而顶级处理程序来得太晚了。

通过从现有类型(例如 class FooPermissionError(OSError, FooError))派生我的模块 foo的特定异常类型,部分地解决了这个问题,但这并不能使用新类型包装现有的异常实例或修改消息变得更容易。

Python 的 PEP 3134“异常链和嵌入式回溯”讨论了 Python 3.0中接受的“链接”异常对象的更改,以指示在处理现有异常时引发了新的异常。

我正在尝试做的事情是相关的: 我需要它在早期的 Python 版本中也能工作,而且我不需要它来链接,只需要它来实现多态性。做这件事的正确方法是什么?

73494 次浏览

您可以创建自己的异常类型,以扩展已捕获的 无论哪个例外

class NewException(CaughtException):
def __init__(self, caught):
self.caught = caught


try:
...
except CaughtException as e:
...
raise NewException(e)

但在大多数情况下,我认为捕获异常、处理异常以及将 raise作为原始异常(并保留回溯)或 raise NewException()更为简单。如果我调用您的代码,并且我收到了您的一个自定义异常,我希望您的代码已经处理了您必须捕获的任何异常。因此,我不需要自己访问它。

编辑: 我发现 这个分析的方法抛出自己的异常和保持原来的异常。没有漂亮的解决方案。

您可以使用 sys.exc _ info ()获取回溯,并使用所述回溯引发新的异常(正如 PEP 所提到的)。如果希望保留旧的类型和消息,可以对异常执行此操作,但只有在捕获异常的程序寻找它时才有用。

比如说

import sys


def failure():
try: 1/0
except ZeroDivisionError, e:
type, value, traceback = sys.exc_info()
raise ValueError, ("You did something wrong!", type, value), traceback

当然,这真的没什么用。如果是的话,我们就不需要 PEP 了。我不建议你这么做。

Python 3 引入了 异常链(如 PEP 3134所述)。这允许在引发异常时引用现有的异常作为“原因”:

try:
frobnicate()
except KeyError as exc:
raise ValueError("Bad grape") from exc

捕获的异常(exc,KeyError)因此成为新异常 ValueError 的一部分(是“原因”)。任何捕获新异常的代码都可以使用“ cause”。

通过使用这个特性,可以设置 __cause__属性。


巨蟒2中,似乎这个用例没有好的答案(如 伊恩 · 比金Ned Batchelder所描述的)。

解决你的需求的最直接的办法应该是:

try:
upload(file_id)
except Exception as upload_error:
error_msg = "Your upload failed! File: " + file_id
raise RuntimeError(error_msg, upload_error)

通过这种方式,您可以稍后打印您的消息和上传函数抛出的特定错误

我还发现,很多时候我需要一些“包装”的错误提出。

这包括在函数作用域中,有时只包装函数内的一些行。

创建了一个用于 decoratorcontext manager的包装器:


实施

import inspect
from contextlib import contextmanager, ContextDecorator
import functools


class wrap_exceptions(ContextDecorator):
def __init__(self, wrapper_exc, *wrapped_exc):
self.wrapper_exc = wrapper_exc
self.wrapped_exc = wrapped_exc


def __enter__(self):
pass


def __exit__(self, exc_type, exc_val, exc_tb):
if not exc_type:
return
try:
raise exc_val
except self.wrapped_exc:
raise self.wrapper_exc from exc_val


def __gen_wrapper(self, f, *args, **kwargs):
with self:
for res in f(*args, **kwargs):
yield res


def __call__(self, f):
@functools.wraps(f)
def wrapper(*args, **kw):
with self:
if inspect.isgeneratorfunction(f):
return self.__gen_wrapper(f, *args, **kw)
else:
return f(*args, **kw)
return wrapper

用法示例

装潢师

@wrap_exceptions(MyError, IndexError)
def do():
pass

当调用 do方法时,不要担心 IndexError,只需要 MyError

try:
do()
except MyError as my_err:
pass # handle error

上下文管理器

def do2():
print('do2')
with wrap_exceptions(MyError, IndexError):
do()

do2中,在 context manager中,如果 IndexError被抬起,它将 包裹并提升 MyError