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

big*_*ose 128 python polymorphism exception-handling

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

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

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

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

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

big*_*ose 177

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

try:
    frobnicate()
except KeyError as exc:
    raise ValueError("Bad grape") from exc
Run Code Online (Sandbox Code Playgroud)

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

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


Python 2中,看起来这个用例没有好的答案(如Ian BickingNed Batchelder所描述的).游民.

  • 异常链接实际上是现在的默认行为,实际上它与问题相反,抑制了需要工作的第一个异常,请参阅PEP 409 https://www.python.org/dev/peps/pep-0409/ (4认同)
  • Ian Bicking不描述我的解决方案吗?我很遗憾我给出了一个如此神圣的回答,但是这个被接受是很奇怪的. (3认同)

Dev*_*rre 36

您可以使用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
Run Code Online (Sandbox Code Playgroud)

当然,这真的没那么有用.如果是,我们就不需要那个PEP.我不建议这样做.

  • 有关从两个开明的pythonistas重新引发Python异常的不同方法的更多细节:[Ian Bicking](http://blog.ianbicking.org/2007/09/12/re-raising-exceptions/)和[Ned Batchelder] (http://nedbatchelder.com/blog/200711/rethrowing_exceptions_in_python.html) (5认同)
  • Arafangion可能是指[sys.exc_info()`]的Python文档中的警告(http://docs.python.org/library/sys.html#sys.exc_info),@Devin.它说,"将traceback返回值分配给处理异常的函数中的局部变量将导致循环引用." 但是,下面的注释表明,自Python 2.2以来,可以清理循环,但是避免循环会更有效. (4认同)
  • 我没有存储任何东西,我将追溯作为一个局部变量,可能会超出范围.是的,可以想象它没有,但如果你在全球范围内而不是在功能范围内提出类似的例外,那么你就会遇到更大的问题.如果您的投诉仅仅是它可以在全球范围内执行,那么正确的解决方案不是添加必须解释的无关的样板,并且与99%的用途无关,而是重写解决方案以便不会出现这种情况这是必要的,因为它似乎没有什么不同 - 正如我现在所做的那样. (2认同)

Nik*_*iah 11

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

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

try:
    ...
except CaughtException as e:
    ...
    raise NewException(e)
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我描述的用例不是用于“处理”异常;而是用于“处理”异常。它具体是关于“不”处理它,而是添加一些额外的信息(一个附加的类和一条新消息),以便可以在调用堆栈中进一步处理它。 (2认同)

Aar*_*_ab 6

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

这既包含在函数作用域中,有时也只包含函数内的某些行。

创建了一个用于decoratorand的包装器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
Run Code Online (Sandbox Code Playgroud)

使用示例

装饰者

@wrap_exceptions(MyError, IndexError)
def do():
   pass
Run Code Online (Sandbox Code Playgroud)

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

try:
   do()
except MyError as my_err:
   pass # handle error 
Run Code Online (Sandbox Code Playgroud)

上下文管理器

def do2():
   print('do2')
   with wrap_exceptions(MyError, IndexError):
       do()
Run Code Online (Sandbox Code Playgroud)

inside do2, in context manager, ifIndexError被举起,则会被包裹并举起MyError