在Python中封装异常

5 投票
2 回答
2050 浏览
提问于 2025-05-01 03:17

如何让应用程序与来自所用库的异常解耦?

[app] --uses--> [lib] --dependson--> [dependency]
                                           / /
  x- <-propagates--o <---throwsexception--' /
   \                                       /
    `-----needstohandle,soimports-----> --'

这个问题来源于真实的 pip 代码:

  1. 模块 A (req/req_set.py) 依赖于模块 B
  2. 模块 B (下载) 使用模块 C (requests)
  3. 模块 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,你也可以访问它。

撰写回答