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

时间:2009-03-30 05:04:34

标签: python exception-handling polymorphism

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

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

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

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

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

5 个答案:

答案 0 :(得分:150)

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

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

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

通过使用此功能,设置了__cause__属性。内置的异常处理程序knows how to report the exception's “cause” and “context”以及回溯。


Python 2 中,似乎此用例没有好的答案(如Ian BickingNed Batchelder所述)。长号。

答案 1 :(得分:34)

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

答案 2 :(得分:10)

您可以创建自己的例外类型,扩展您已捕获的whichever exception

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

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

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

编辑:我找到了this analysis种方法来抛出自己的异常并保留原始异常。没有漂亮的解决方案。

答案 3 :(得分:1)

我还发现,很多时候我都需要对出现的错误进行“包装”。

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

创建了一个包装,用于var xAxis = d3.axisBottom(x).ticks(5); var yAxis = d3.axisLeft(y).ticks(5); decorator


实施

context 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

答案 4 :(得分:-1)

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

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

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