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

Re-raise exception with a different type and message, preserving existing information

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

我需要的是对捕获的异常进行"包装",使其具有不同的类型和消息,以便通过捕获异常的内容在传播层次结构上进一步提供信息。但我不想丢失现有的类型、消息和堆栈跟踪;这对于试图调试问题的人来说都是有用的信息。一个顶级的异常处理程序是不好的,因为我正在尝试在异常进一步向上传播堆栈之前对其进行修饰,而顶级处理程序太晚了。

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

python的pep 3134"异常链接和嵌入式跟踪"讨论了python 3.0中为"链接"异常对象接受的更改,以指示在处理现有异常期间引发了新的异常。

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


python3引入了异常链接(如pep 3134中所述)。这允许在引发异常时将现有异常作为"原因"引用:

1
2
3
4
try:
    frobnicate()
except KeyError as exc:
    raise ValueError("Bad grape") from exc

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

通过使用此功能,可以设置__cause__属性。内置异常处理程序还知道如何报告异常的"原因"和"上下文"以及追溯。

在Python2中,这个用例似乎没有很好的答案(如IanBicking和NedBatchelder所描述)。真倒霉。


您可以使用sys.exc_info()获取跟踪,并使用所述跟踪引发新的异常(如PEP提到的)。如果要保留旧的类型和消息,可以在异常上执行此操作,但只有当捕获异常的对象查找它时,这才有用。

例如

1
2
3
4
5
6
7
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

当然,这真的没那么有用。如果是的话,我们就不需要那种活力了。我不建议你这么做。


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

1
2
3
4
5
6
7
8
9
class NewException(CaughtException):
    def __init__(self, caught):
        self.caught = caught

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

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

编辑:我发现了这种分析方法,可以抛出自己的异常并保留原来的异常。没有很好的解决方案。


我还发现,很多时候我需要一些"包装"来纠正错误。

这包含在函数范围内,有时只在函数内换行。

创建了一个包装,用于decoratorcontext manager

实施

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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

使用实例装饰者

1
2
3
@wrap_exceptions(MyError, IndexError)
def do():
   pass

调用do方法时,不要担心IndexError的问题,只要MyError即可。

1
2
3
4
try:
   do()
except MyError as my_err:
   pass # handle error

上下文管理器

1
2
3
4
def do2():
   print('do2')
   with wrap_exceptions(MyError, IndexError):
       do()

do2内,在context manager内,如果IndexError升高,它将包起来,抬起来


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

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

通过这种方式,您可以稍后打印您的消息,以及上载函数所引发的特定错误。