在Python中封装异常
如何让应用程序与来自所用库的异常解耦?
[app] --uses--> [lib] --dependson--> [dependency]
/ /
x- <-propagates--o <---throwsexception--' /
\ /
`-----needstohandle,soimports-----> --'
这个问题来源于真实的 pip
代码:
- 模块 A (req/req_set.py) 依赖于模块 B
- 模块 B (下载) 使用模块 C (requests)
- 模块 A 导入 模块 C 来处理来自 C 的异常
如何在模块 B 中封装异常?怎样才能让模块 A 不再依赖模块 C?如何确保原始异常的原因和细节不会丢失?换句话说,我该如何用不同的名称重新抛出异常?
下面的代码片段实现了所需的功能,但仅适用于 Python 3:
try:
dependency_call()
except DependencyError as exc:
raise LibraryError from exc
更新:我在寻找兼容 Python 2 的解决方案,Python 3 添加的 raise ... from ...
几乎可以完美解决这个问题。
更新 2:封装异常的目标是能够在 [lib]
中捕获它,并重新抛出一个新的异常到 [app]
,同时保留堆栈跟踪,以便调试工具仍然可以追踪代码(对于仅限人类的解决方案,Alex Thornton 的回答 应该不错)。
2 个回答
0
如果我理解正确的话,你想要更强地解耦,并消除:
from pip._vendor import requests
还有:
except requests.HTTPError as exc:
你可以通过引入一个最后的备用处理程序,作为所有异常处理的最后一个except
来实现这一点,比如:
try:...
except A:
... # here all your other exceptions
except Exception as exc: # the fall-back handler
if "HTTPError" in repr(exc):
# do whatever you want to do with the (assumed) request.HTTPError
# any other Exception with HTTPError in it's repr-string will be
# caught here
不过,这样做的缺点是,它仍然是紧密耦合的,明显违反了“德梅特法则”,因为你需要了解一个对象的一些内部细节,而这个对象甚至不在你的对象组合中。所以在某种意义上,这样反而更糟糕。
4
你可以通过引用 Exception
这个基础类来捕捉任何类型的异常:
except Exception as exc:
raise ApplicationError from exc
如果你想在 Python 2 中使用 from
这个用法,你需要对你自定义的异常进行一些调整:
class ApplicationError(Exception):
def __init__(self, cause, trace):
self.cause = cause
self.trace = trace
def __str__(self):
return '{origin}\nFrom {parent}'.format(origin=self.trace,
parent=self.cause)
然后像这样抛出它:
except Exception, exc:
raise ApplicationError(exc)
这样在抛出异常时,它会打印出 cause
,这个也是一个属性,如果你决定捕捉 ApplicationError
,你也可以访问它。