Python中多个except块必须按具体到宽泛的顺序排列,否则父类异常会提前捕获子类异常;可用元组合并同类处理的异常;裸raise保留原始traceback;Python 3.11+支持ExceptionGroup和except*处理并发多异常。

多个 except 块的顺序为什么不能乱
Python 按 except 出现的顺序从上到下匹配异常类型,一旦匹配成功就不再检查后面的块。如果把宽泛的父类异常(如 Exception)写在前面,它会提前捕获所有子类异常(比如 ValueError、TypeError),导致后续更具体的 except 形同虚设。
常见错误现象:ValueError 永远进不到对应分支,全被上面的 except Exception: 吞掉了。
- 总是把更具体的异常类型放在前面,比如先写
except ValueError:,再写except KeyError: - 把
except Exception:放在最后兜底,仅用于处理未预期的异常 - 避免直接
except:(空 except),它连KeyboardInterrupt和SystemExit都会拦截,可能破坏程序退出逻辑
用元组一次声明多个同类处理逻辑的异常
当多个异常需要执行完全相同的恢复或日志动作时,不必重复写多个 except 块,用元组把它们括起来即可——这是语法支持的简洁写法,不是“偷懒”,而是明确表达“这些异常等价处理”。
使用场景:读配置文件时,FileNotFoundError、PermissionError、IsADirectoryError 都意味着“文件不可用”,统一返回默认配置更合理。
立即学习“Python免费学习笔记(深入)”;
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
try:
with open("config.json") as f:
return json.load(f)
except (FileNotFoundError, PermissionError, IsADirectoryError) as e:
logging.warning("Config load failed: %s, using defaults", e)
return DEFAULT_CONFIG
- 元组内异常类型之间是“或”关系,满足任一即触发该
except - 仍可正常访问
e.args或调用str(e)获取原始错误信息 - 不适用于需要差异化处理的异常;若某一个异常要额外重试,就得分拆开写
在 except 中重新抛出异常并保留原始 traceback
有时你得先做点清理(比如关文件、回滚事务),再让异常继续向上冒泡——这时不能简单写 raise,否则会丢失原始发生位置;也不能写 raise e,那会覆盖掉原始 traceback。
正确做法是裸 raise(不带参数),它会原样抛出当前正在处理的那个异常,包括完整的栈帧。
try:
conn = database.connect()
do_something(conn)
except DatabaseError as e:
conn.close() # 必须清理
raise # ← 关键:不是 raise e,也不是 raise DatabaseError(...)
- 裸
raise是唯一能保留原始 traceback 的方式 - 如果想补充上下文但又不掩盖源头,可用
raise NewException(...) from e(PEP 3134),此时会显示 “The above exception was the direct cause of the following exception” - 注意:在
except块里执行raise前,确保清理逻辑不会引发新异常,否则原始异常就真丢了
用 ExceptionGroup 和 except* 处理并发任务中的多异常(Python 3.11+)
传统 try/except 在 asyncio.gather() 或 concurrent.futures 场景下很无力:一个任务失败,整个集合就中断,或者只暴露一个异常,其余全丢。
Python 3.11 引入 ExceptionGroup 和结构化异常匹配 except*,专为这种“一批操作中部分失败”的情况设计。
try:
results = await asyncio.gather(
fetch("https://a.com"),
fetch("https://b.com"),
fetch("https://c.com"),
return_exceptions=True
)
except* ConnectionError as eg:
logging.error("Network issues in %d tasks", len(eg.exceptions))
# eg.exceptions 是 ConnectionError 实例列表
except* TimeoutError:
# 单独处理超时
pass
-
except*匹配的是ExceptionGroup中“至少一个”满足条件的子异常,不是全部 - 必须配合
return_exceptions=True(gather)或显式构造ExceptionGroup才能触发 - 低于 3.11 的环境只能靠手动收集异常列表 + 事后遍历判断,没有语言级支持
真正麻烦的从来不是“怎么写多个 except”,而是想清楚哪些异常该吞、哪些该透传、哪些要改写成业务语义——异常流是控制流的一部分,不是错误日志的收纳箱。

















