如何为Python异常实现恢复语义?Validator类场景解析
我有一个Validator类,其validate方法会执行多项检查,可能抛出不同异常:
class Validator: def validate(self, something) -> None: if a: raise ErrorA() if b: raise ErrorB() if c: raise ErrorC()
现在需要在外部调用时定制其行为:阻止ErrorB抛出,但不阻止ErrorC。类似异常恢复语义的机制会很有用,但还没找到合适的实现方式。
说明:我拥有Validator类的源码控制权,但希望尽可能保留其现有接口。
以下是我考虑过的几种可行方案:
1. 直观异常捕获方案
try: validator.validate(something) except ErrorB: pass
问题:不可行。因为当ErrorB和ErrorC都满足抛出条件时,捕获ErrorB后会直接终止后续检查,导致ErrorC也被抑制。
2. 复制方法并移除目标检查
# 调用方模块中 class CustomValidator(Validator): def validate(self, something) -> None: if a: raise ErrorA() if c: raise ErrorC()
问题:复制检查逻辑不是好办法,一旦Validator类的检查逻辑变更,CustomValidator会因未同步更新而引入bug。
3. 将检查拆分为独立方法
class Validator: def validate(self, something) -> None: self.validate_a(something) self.validate_b(something) self.validate_c(something) def validate_a(self, something) -> None: if a: raise ErrorA() def validate_b(self, something) -> None: if b: raise ErrorB() def validate_c(self, something) -> None: if c: raise ErrorC() # 调用方模块中 class CustomValidator(Validator): def validate(self, something) -> None: super().validate_a(something) super().validate_c(something)
问题:只是略优的复制方案,如果后续Validator新增validate_d()这类检查方法,CustomValidator会遗漏新的检查,引发bug。
4. 手动添加异常抑制逻辑
import contextlib class Validator: def validate(self, something, *, suppress: list[type[Exception]] = []) -> None: if a: self._raise(ErrorA(), suppress) if b: self._raise(ErrorB(), suppress) if c: self._raise(ErrorC(), suppress) def _raise(self, e: Exception, suppress: list[type[Exception]]) -> None: with contextlib.suppress(*suppress): raise e
评价:这是我目前倾向的方案。虽然新增了可选参数,且抛出异常的语法略显繁琐,但整体实现成本可接受,同时能保留原有接口的核心用法,调用时只需指定要抑制的异常类型:
validator.validate(something, suppress=[ErrorB])
5. 添加禁用检查的开关参数
class Validator: def validate(self, something, *, check_a: bool = True, check_b: bool = True, check_c: bool = True) -> None: if check_a and a: raise ErrorA() if check_b and b: raise ErrorB() if check_c and c: raise ErrorC()
优缺点:优势是可以精细控制每个检查项,即便多个检查抛出相同异常也能区分控制;但缺点是代码冗长,且每次新增检查项都要同步添加对应的开关参数,维护成本高——而我实际的检查项远不止三个。调用示例:
validator.validate(something, check_b=False)
6. 以生成器形式返回异常
from typing import Iterator class Validator: def validate(self, something) -> Iterator[Exception]: if a: yield ErrorA() if b: yield ErrorB() if c: yield ErrorC()
问题:会对现有调用者造成破坏性变更,原本直接调用validator.validate(something)就能抛出第一个异常的逻辑,现在需要手动处理:
# 替代原来的 validator.validate(something) e = next(validator.validate(something), None) if e is not None: raise e
即便做向后兼容,新增一个iter_errors方法:
class Validator: def validate(self, something) -> None: e = next(self.iter_errors(something), None) if e is not None: raise e def iter_errors(self, something) -> Iterator[Exception]: if a: yield ErrorA() if b: yield ErrorB() if c: yield ErrorC()
调用者要实现抑制ErrorB的逻辑依然繁琐:
exceptions = validator.iter_errors(something) e = next(exceptions, None) if isinstance(e, ErrorB): # 跳过ErrorB,不抛出 e = next(exceptions, None) if e is not None: raise e
对比方案4和5的调用方式,这种实现的易用性明显不足。
内容的提问来源于stack exchange,提问作者Expurple

