我正在编写一个模块,并希望为它可以引发的异常(例如,从FooError
抽象类继承所有foo
模块的特定异常)建立一个统一的异常层次结构。这允许模块的用户捕获那些特定的异常并在需要时清楚地处理它们。但是从模块中引发的许多异常都是由于其他一些异常而引发的;例如,由于文件上的 OSError 导致某些任务失败。
我需要的是“包装”捕获的异常,使其具有不同的类型和消息,以便通过捕获异常的任何内容在传播层次结构中进一步获取信息。但我不想丢失现有的类型、消息和堆栈跟踪;对于试图调试问题的人来说,这些都是有用的信息。顶级异常处理程序不好,因为我试图在异常进一步向上传播堆栈之前装饰异常,而顶级处理程序为时已晚。
这部分通过从现有类型(例如 class FooPermissionError(OSError, FooError)
)派生我的模块 foo
的特定异常类型来解决,但这并没有使将现有异常实例包装在新类型中变得更容易,也不能修改信息。
Python 的PEP 3134“异常链接和嵌入式回溯”讨论了 Python 3.0 中接受的“链接”异常对象的更改,以表明在处理现有异常期间引发了新异常。
我正在尝试做的是相关的:我需要它也可以在早期的 Python 版本中工作,而且我不需要它用于链接,而仅用于多态性。这样做的正确方法是什么?
except Exception as e
--> raise type(e), type(e)(e.message + custom_message), sys.exc_info()[2]
--> this solution is from another SO question。这不是很漂亮,但很实用。
Python 3 引入了异常链接(如 PEP 3134 中所述)。这允许在引发异常时引用现有异常作为“原因”:
try:
frobnicate()
except KeyError as exc:
raise ValueError("Bad grape") from exc
捕获的异常(exc
,一个 KeyError)因此成为新异常 ValueError 的一部分(是“原因”)。 “原因”可用于捕获新异常的任何代码。
通过使用此功能,可以设置 __cause__
属性。内置异常处理程序还 knows how to report the exception's “cause” and “context” 以及回溯。
在 Python 2 中,这个用例似乎没有很好的答案(如 Ian Bicking 和 Ned Batchelder 所述)。真可惜。
您可以使用 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。我不建议这样做。
sys.exc_info()
中的警告,@Devin。它说,“将回溯返回值分配给正在处理异常的函数中的局部变量将导致循环引用。”但是,下面的注释说,从 Python 2.2 开始,可以清理循环,但避免它更有效。
您可以创建自己的异常类型来扩展您捕获的 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 种方法来抛出您自己的异常并保留原始异常。没有漂亮的解决方案。
我还发现很多时候我需要对引发的错误进行一些“包装”。
这包括在函数范围内,有时只在函数内包装一些行。
创建了一个用于 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
满足您需求的最直接的解决方案应该是:
try:
upload(file_id)
except Exception as upload_error:
error_msg = "Your upload failed! File: " + file_id
raise RuntimeError(error_msg, upload_error)
通过这种方式,您可以稍后打印您的消息以及上传功能抛出的特定错误
try: return 2 / 0 except ZeroDivisionError as e: raise ValueError(e)