164

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

我需要的是“包装”捕获的异常,使其具有不同的 type 和 message,以便通过捕获异常的任何内容在传播层次结构中进一步获取信息。但我不想丢失现有的类型、消息和堆栈跟踪;对于试图调试问题的人来说,这些都是有用的信息。顶级异常处理程序不好,因为我试图在异常进一步向上传播堆栈之前装饰异常,而顶级处理程序为时已晚。

这部分通过foo从现有类型(例如class FooPermissionError(OSError, FooError))派生我的模块的特定异常类型来解决,但这并没有使将现有异常实例包装在新类型中变得更容易,也没有修改消息。

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

我正在尝试做的是相关的:我需要它也可以在早期的 Python 版本中工作,而且我不需要它用于链接,而仅用于多态性。这样做的正确方法是什么?

4

5 回答 5

259

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

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

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

通过使用此功能,设置__cause__属性。内置的异常处理程序还知道如何报告异常的“原因”和“上下文”</a>以及回溯。


Python 2中,这个用例似乎没有很好的答案(如Ian BickingNed Batchelder所述)。真可惜。

于 2009-04-27T03:25:42.617 回答
39

您可以使用 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。我不建议这样做。

于 2009-03-30T05:40:08.753 回答
15

您可以创建自己的异常类型来扩展您捕获的任何异常。

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

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

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

编辑:我发现了对抛出自己的异常并保留原始异常的方法的分析。没有漂亮的解决方案。

于 2009-03-30T05:38:00.817 回答
5

我还发现很多时候我需要对引发的错误进行一些“包装”。

这包括在函数范围内,有时只在函数内包装一些行。

创建了一个包装器来使用 adecoratorcontext 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()

inside do2, in the context manager, if IndexErroris raise 会被包裹起来MyError

于 2018-12-29T18:08:12.033 回答
-2

满足您需求的最直接的解决方案应该是:

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

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

于 2015-12-17T18:06:56.950 回答