多个异常捕获与 except 块顺序
在实际项目中,一个 try 块可能抛出好几种异常。比如我写过的一个网络请求模块,超时、连接失败、数据格式错误……全得分别处理。这时候就要用多个 except 块。但顺序可不是随便写的,排错了,轻则白白浪费代码,重则隐藏严重 bug。
基本写法:多个 except 块
语法很简单,你在 try 后面堆多个 except,每个指定一个异常类型:
try:
result = dangerous_operation()
except ValueError:
print("值不合法")
except TypeError:
print("类型不对")
except ZeroDivisionError:
print("不能除以零")
运行时,Python 从上到下依次检查每个 except 块。第一个匹配的就会被执行,剩下的全部跳过。
except Exception as e 一把抓——那样你根本不知道到底出了啥问题。调试的时候头皮发麻。顺序的潜规则:子类在前,父类在后
这里有个很多新手摔过跤的坑。异常类型是有继承关系的。拿 Python 内置异常来说,ZeroDivisionError 是 ArithmeticError 的子类,ArithmeticError 又是 Exception 的子类。
如果你把父类写在前面,子类就永远不会被触发。
# ❌ 错误的顺序
try:
x = 10 / 0
except Exception:
print("捕获到通用异常")
except ZeroDivisionError:
print("除以零异常") # 这行永远不会执行
运行上面代码,只会输出「捕获到通用异常」。你想想看,ZeroDivisionError 本身就是 Exception 的子类,第一个 except Exception 已经把它接住了,后面的块形同虚设。
正确的写法是:
# ✅ 正确的顺序
try:
x = 10 / 0
except ZeroDivisionError:
print("除以零异常")
except Exception:
print("其它异常")
except Exception 作为最后一道防线,放在所有具体异常之后。这样既能精细处理已知问题,又不会漏掉意外情况。我曾经踩过的坑
几年前维护一个数据处理流水线,跑批量任务偶尔报错。我排查了很久,日志里只看到「处理失败,请重试」——因为代码里只有一个 except Exception,把 KeyError、IndexError、ValueError 全吞了,根本看不出根因。后来我改成按具体异常分别处理,顺序也排好,问题一分钟就定位了。
那之后我养成了个习惯:每个 try 块里的 except 顺序,我都会专门检查一遍,尤其是当涉及多层继承的异常时。
多级异常捕获流程示意图
异常继承链与陷阱示例
看这个表,你就明白为啥顺序那么重要:
| 异常类型 | 父类 | 正确捕获顺序 |
|---|---|---|
ZeroDivisionError | ArithmeticError | 第 1 位 |
OverflowError | ArithmeticError | 第 2 位 |
ArithmeticError | Exception | 第 3 位 |
Exception | BaseException | 最后(第 4 位) |
如果你把 ArithmeticError 放在 ZeroDivisionError 前面,所有算数相关异常都会被它截胡,具体的子类就永远没机会了。
except:(不指定异常类型)——这会捕获 SystemExit、KeyboardInterrupt 等严重异常,导致程序无法正常退出。真要兜底,用 except Exception 就够了。实践建议:如何排好顺序
- 先列所有可能的具体异常:写代码前把会抛出的异常类型列出来(比如
ValueError、KeyError、ConnectionError)。 - 按继承层次从底向上排:最具体的子类放最上面,逐层往上,最后是
Exception。 - 同一层次的随意排:比如
ValueError和TypeError没有继承关系,顺序无所谓。 - 记得加日志:每个 except 块里至少记录一下异常信息,方便事后分析。
举个例子,这是我常用的模板:
try:
data = json.loads(raw)
process(data)
except json.JSONDecodeError:
log.error("JSON 格式错误")
except KeyError as e:
log.error(f"缺少字段: {e}")
except ValueError as e:
log.error(f"数据值不合法: {e}")
except Exception as e:
log.exception("未知错误") # 打印完整堆栈
except Exception 兜着,等测试把遗漏的异常炸出来,再逐步添加具体的 except 块。但记住每次新增都要检查顺序,别插错位置。说白了,多个 except 块的顺序就是一道「异常拦截链」。你安排得越精细,代码越健壮;随意排列,就是给自己埋雷。嗯,今天就聊到这,动手试试吧。