Python 内置异常层级结构全解析

7次阅读

最可靠方式是调用BaseException.__subclasses__()递归遍历继承树,因其反映Cpython运行时真实结构,比文档或help更全;except Exception捕不到SystemExit等退出异常,应避免except BaseException。

Python 内置异常层级结构全解析

Python 异常类继承关系怎么查最可靠

直接看 BaseException__subclasses__() 链,比翻文档或记忆更准——因为 CPython 源码里新增异常(比如 Keyboardinterrupt)未必全在文档“标准异常”表里列全,而 __subclasses__() 是运行时真实继承树。

实操建议:

  • 在 Python 交互式环境里执行 BaseException.__subclasses__(),得到顶层子类(如 SystemExitKeyboardInterruptException
  • 对每个子类再调用 __subclasses__(),递归展开(注意避开循环引用,ExceptionBaseException 本身不互为直接父子)
  • 别依赖 help(Exception) 输出的“Subclasses”列表——它只显示模块级定义的类,漏掉动态生成的(如某些库注入的异常)

捕获 ExceptionBaseException 的实际区别

except Exception: 捕不到 SystemExitKeyboardInterruptGeneratorExit;但 except BaseException: 会捕到——这在写守护进程或 CLI 工具时极易出问题:比如你本想优雅退出,却因捕了 BaseExceptionCtrl+C 给吞了,导致程序无法中断。

常见错误现象:

立即学习Python免费学习笔记(深入)”;

  • 脚本加了 except BaseException: 后,Ctrl+C 失效,必须 kill -9
  • 单元测试里用 self.assertRaises(BaseException) 断言失败,因为测试框架本身抛的是 AssertionError(继承自 Exception),不是 BaseException 直系子类

正确做法:除非你在写解释器或信号拦截层,否则一律用 except Exception:;真要处理退出信号,显式捕 KeyboardInterruptSystemExit

raise 时不带参数和带 None 的行为差异

except 块里写 raise(无参数)会原样重抛当前异常,保留原始 traceback;写 raise None 是语法错误,Python 3.10+ 会报 SyntaxError: invalid syntax;而 raise e(其中 e 是捕获的异常实例)会创建新 traceback,丢失原始上下文。

使用场景与风险:

  • 日志后透传异常:用 raise(空 raise),不要 raise e
  • 想改写异常类型但保留 traceback:用 raise NewError(...) from e,触发异常链
  • raise e线程/协程中尤其危险——原始异常可能来自其他上下文,traceback 完全失真

自定义异常该继承 Exception 还是 RuntimeError

绝大多数情况继承 Exception 就够了。继承 RuntimeError 只在明确表示“程序运行时发生了未预期状态,且不属于 I/O、类型、逻辑等已有分类”时才合理——比如异步任务调度器检测到死锁,或 ORM 发现对象状态不一致。

性能与兼容性影响:

  • 所有内置异常检查(如 isinstance(e, Exception))对 Exception 子类天然兼容;若继承 RuntimeError,需额外确认下游是否只检查 Exception
  • 某些框架(如 flask 的错误处理器)默认只捕 Exception 及其子类,不处理 RuntimeError 的子类,除非显式注册
  • 别为了“显得更具体”而深挖继承链——MyappError 继承 Exception 比继承 ValueError 更安全,除非你真要参与 int() 那类函数的错误语义

真正容易被忽略的是:异常类名末尾要不要加 ErrorException。PEP 8 明确建议用 Error(如 ParseError),但很多主流库(requests、sqlalchemy)混用。统一就行,别在同一个项目里既有 ConnectionError 又有 TimeoutException

text=ZqhQzanResources