问题:用其他类型和消息重新引发异常,保留现有信息
我正在编写一个模块,并希望它可以引发的异常具有统一的异常层次结构(例如,从FooError
抽象类继承所有foo
模块的特定异常)。这使模块的用户可以捕获那些特定的异常,并在需要时进行区别处理。但是从模块引发的许多异常是由于其他一些异常而引发的;例如,由于文件上的OSError而导致某些任务失败。
我需要的是“包装”捕获到的异常,使其具有不同的类型和消息,以便通过捕获异常的方式在传播层次结构中进一步获取信息。但是我不想丢失现有的类型,消息和堆栈跟踪;这对于尝试调试问题的人来说都是有用的信息。顶级异常处理程序是不好的,因为我正在尝试在异常传播到传播堆栈之前对其进行装饰,并且顶级处理程序为时已晚。
这可以通过foo
从现有类型(例如class FooPermissionError(OSError, FooError)
)中派生模块的特定异常类型来部分解决,但这并没有使将现有异常实例包装为新类型或修改消息变得更加容易。
Python的PEP 3134 “异常链接和嵌入式回溯”讨论了Python 3.0中接受的“链接”异常对象更改,以指示在处理现有异常期间引发了新异常。
我想做的是相关的:我需要它在早期的Python版本中也能工作,我不需要链,而只需要多态。什么是正确的方法?
回答 0
Python 3引入了异常链接(如PEP 3134中所述)。这允许在引发异常时引用现有异常作为“原因”:
try:
frobnicate()
except KeyError as exc:
raise ValueError("Bad grape") from exc
因此,捕获的异常成为新异常的一部分(是“原因”),并且任何捕获新异常的代码均可使用。
通过使用此功能,__cause__
可以设置属性。内置的异常处理程序还知道如何报告异常的“原因”和“上下文”以及回溯。
在Python 2中,该用例似乎没有很好的答案(如Ian Bicking和Ned Batchelder所述)。笨蛋
回答 1
您可以使用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
class NewException(CaughtException):
def __init__(self, caught):
self.caught = caught
try:
...
except CaughtException as e:
...
raise NewException(e)
但是,大多数情况下,我认为捕获异常,处理raise
异常以及原始异常(并保留回溯)或会更简单raise NewException()
。如果我正在调用您的代码,并且收到了您的自定义异常之一,那么我希望您的代码已经处理了您必须捕获的任何异常。因此,我不需要自己访问它。
编辑:我发现这种分析方法可以引发您自己的异常并保留原始异常。没有漂亮的解决方案。
回答 3
我还发现,很多时候我需要对出现的错误进行“包装”。
这既包含在函数范围内,有时也仅在函数内包含一些行。
创建了要用于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
被抬起,它将被包裹并抬起MyError
回答 4
满足您需求的最直接的解决方案应该是:
try:
upload(file_id)
except Exception as upload_error:
error_msg = "Your upload failed! File: " + file_id
raise RuntimeError(error_msg, upload_error)
这样,您以后就可以打印消息以及上载功能引发的特定错误