Python 异常层次与异常链
内置异常层次、领域异常设计,以及 raise from / __cause__ 保留失败因果链。
#concept
#growing
#dev
#python
#python
[!info] related notes
Python 异常层次与异常链
这个概念为什么出现
线上只看到 ValueError: bad 却不知道是 JSON 解析失败还是业务校验失败,排障成本陡增。需要:
- 合理的异常类型层次
raise New from original保留因果
[!abstract] 一句话理解 异常是类层次;捕获应按具体程度排列;
raise ... from ...建立__cause__链,便于日志与except策略。
最小可运行示例
先把示例放进业务场景,再看代码:
场景:支付回调解析失败包装为领域错误
# payment_exc_chain.py
# 业务意图:底层 JSON 错误包装为 PaymentCallbackError,保留 cause。
# 教学点:
# - 自定义异常;
# - raise from;
# - __cause__。
import json
class PaymentCallbackError(Exception):
"""支付回调无法被接受。"""
def parse_callback(raw: str) -> dict:
try:
data = json.loads(raw)
except json.JSONDecodeError as exc:
raise PaymentCallbackError("invalid callback json") from exc
if not isinstance(data, dict):
raise PaymentCallbackError("callback must be a JSON object")
payment_id = data.get("payment_id")
if not isinstance(payment_id, str) or not payment_id.strip():
raise PaymentCallbackError("payment_id must be a nonempty string")
return data
def main() -> None:
try:
parse_callback("{")
except PaymentCallbackError as exc:
print("type:", type(exc).__name__)
print("msg:", exc)
print("cause:", type(exc.__cause__).__name__, exc.__cause__)
if __name__ == "__main__":
main()
建议运行:
python payment_exc_chain.py
期望输出:
type: PaymentCallbackError
msg: invalid callback json
cause: JSONDecodeError Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
结合场景再看三个关注点
- 对预期 JSON 语法/字段失败,调用方收到 PaymentCallbackError;显式 cause 保留 JSONDecodeError。except 按当前异常类型匹配,不会自动沿 cause 链捕获底层类型。
- JSON 根值也可能是 null、数组或数字;先验证对象形状再读字段。本例只检查 payment_id 是非空字符串,不实现签名校验、重复回调处理或完整支付协议。
- 日志应打印链,而不是只
str(exc)。
核心概念与准确模型
层次(节选)
BaseException → Exception → ValueError / TypeError / OSError / RuntimeError …
- 不要捕获
BaseException(会含 KeyboardInterrupt 等) - 库错误优先继承
Exception
链式
| 形式 | 含义 |
|---|---|
raise X from e | 显式 cause |
raise X from None | 抑制隐式上下文的默认展示,__context__ 通常仍可访问 |
| 在 except 中 raise X | 可能带 __context__ |
边界情况与反直觉行为
- from None 设置
__suppress_context__,不会擦除原异常对象;显式 from e 设置__cause__,默认 traceback 优先展示该因果链。 - 多个 except 顺序:先子类后父类。
- ExceptionGroup(3.11+)可组合多个 Exception,并不限于并发;普通 except 捕获组对象,except* 按成员类型拆分处理,未处理部分继续传播。
常见误区
[!warning] 常见误区:所有失败都 ValueError 错误理解:省事。
正确模型:调用方需要分支的失败应可区分类型。
工程实践
- 包内定义
PackageError基类 - 边界翻译异常,核心少捕多抛
- 测试断言类型与 cause
本节总结
层次服务语义,链接服务排障。二者一起才构成可运营错误模型。
自测题
- 为什么不建议
except BaseException? raise X from e的价值?
参考答案
- 会捕获系统退出/中断类异常,干扰正常控制。
- 保留根因,便于日志与上层决策。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| Exception hierarchy | 文档 | 内置树 |
| raise | 规范 | from 子句 |