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

jpfvwuh4  于 2023-02-28  发布在  Python
关注(0)|答案(7)|浏览(128)

我正在写一个模块,希望有一个统一的异常层次结构来处理它可能引发的异常(例如,从FooError抽象类继承foo模块的所有特定异常),这允许模块的用户捕获这些特定的异常,并在需要时区别处理它们,但是从模块引发的许多异常是因为其他异常而引发的;例如,由于文件上的OSError而导致某个任务失败。
我需要的是**“ Package ”捕获的异常,使其具有不同的类型和消息**,这样无论捕获异常的是什么,信息都可以在传播层次结构的更上层得到,但我不想丢失现有的类型、消息和堆栈跟踪;对于试图调试问题的人来说,这些都是有用的信息,顶级异常处理程序是不好的,因为我试图在异常进入传播堆栈之前修饰它,而顶级处理程序为时已晚。
通过从现有类型(例如class FooPermissionError(OSError, FooError))派生模块foo的特定异常类型,可以部分解决这一问题,但这并不能使将现有异常示例 Package 为新类型或修改消息变得更容易。
Python的PEP 3134“异常链接和嵌入式回溯”讨论了Python 3.0中接受的一个改变,即“链接”异常对象,以指示在处理现有异常的过程中引发了一个新的异常。
我想做的是相关的:我需要它在Python早期版本中也能工作,而且我不需要它来链接,而只需要它来多态。

b4lqfgs4

b4lqfgs41#

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

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

捕获的异常(exc,一个KeyError)因此成为新异常ValueError的一部分(是其“原因”)。“原因”可用于捕获新异常的任何代码。
通过使用这个特性,__cause__属性被设置,内置的异常处理器也知道如何报告异常的“原因”和“上下文”沿着回溯。
Python 2中,这个用例似乎没有好的答案(如Ian BickingNed Batchelder所描述的)。

4dc9hkyq

4dc9hkyq2#

你可以使用sys.exc_info()来获取traceback,然后用traceback引发你的新异常(正如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了。我不建议这么做。

cld4siwp

cld4siwp3#

您可以创建自己的异常类型来扩展捕获的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的方法来抛出自己的异常并保留原来的异常。没有漂亮的解决方案。

gajydyqb

gajydyqb4#

我还发现,很多时候我需要一些" Package "的错误提出。
这包括在函数作用域中,有时只 Package 函数中的某些行。
已创建用于decoratorcontext manager的 Package 器:

实施

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被提升,则它将被 Package 并提升MyError

sigwle7e

sigwle7e5#

这是题外话,但是在为my own library构建一致的错误消息时,我发现我们在自己的错误消息出现时 Package 了它们,很高兴看到Python 3.11现在提供了add_note函数,用附加信息来增加现有的错误,这可能也是有用的。

对于Python 3.11add_note()

add_note()方法被添加到BaseException中。它可以用来丰富异常的上下文信息,这些信息在引发异常时不可用。添加的注解显示在默认的追溯中。
因此,我们现在遵循的模式是:

try:
   some_risky_business()
except MyCustomException as ce:
  ce.add_note(f"Here is some more critical context!")
  raise se
except Exception as e:
  raise MyCustomException("Wow, didn't expect this error.") from e
eoxn13cs

eoxn13cs6#

为了真正“转换”异常,避免@bignose的答案所解释的上下文或原因,你必须做一些恶作剧(下面的Python 3):

import sys

new_ex = None

try:
    something_that_raises_ValueError()
except ValueError:
    _, _, tb = sys.exc_info()
    new_ex = TypeError('This is really how I want to report this')

if new_ex is not None:
    raise new_ex.with_traceback(tb)

通过传入traceback,可以使它指向问题发生的位置,而不是raise语句。
这可能会变成一个上下文,使其更可重用。
请注意,如果您只想更改消息,则可以操作args

def append_message(e_: Exception, msg: str):
    """
    Appends `msg` to the message text of the exception `e`.

    Parameters
    ----------
    e_: Exception
        An exception instance whose `args` will be modified to include `msg`.

    msg: str
        The string to append to the message.
    """
    if e_.args:
        # Exception was raised with arguments
        e_.args = (str(e_.args[0]) + msg,) + e_.args[1:]
    else:
        e_.args = (msg,)

def replace_message(e_: Exception, msg: str):
    """
    Replaces the exception message with `msg`.

    Parameters
    ----------
    e_: Exception
        An exception instance whose `args` will be modified to be `msg`.

    msg: str
        The string to replace the exception message with.
    """
    if e_.args:
        e_.args = (msg,) + e_.args[1:]
    else:
        e_.args = (msg,)
fgw7neuy

fgw7neuy7#

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

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

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

相关问题