多个异常捕获与 except 块顺序

在实际项目中,一个 try 块可能抛出好几种异常。比如我写过的一个网络请求模块,超时、连接失败、数据格式错误……全得分别处理。这时候就要用多个 except 块。但顺序可不是随便写的,排错了,轻则白白浪费代码,重则隐藏严重 bug。

基本写法:多个 except 块

语法很简单,你在 try 后面堆多个 except,每个指定一个异常类型:

try:
    result = dangerous_operation()
except ValueError:
    print("值不合法")
except TypeError:
    print("类型不对")
except ZeroDivisionError:
    print("不能除以零")

运行时,Python 从上到下依次检查每个 except 块。第一个匹配的就会被执行,剩下的全部跳过。

我的小建议:每个 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 顺序,我都会专门检查一遍,尤其是当涉及多层继承的异常时。

多级异常捕获流程示意图

多个 except 块执行流程与顺序规则 try 块中抛出异常 except ZeroDivisionError 是否匹配? 执行并跳过后续 except except ValueError 是否匹配? 执行并跳过后续 except except Exception 兜底处理 执行(仅此一个块) 重要顺序规则 1. 子类异常在前 2. 父类异常在后 3. 具体异常优先 4. Exception最后写

异常继承链与陷阱示例

看这个表,你就明白为啥顺序那么重要:

异常类型父类正确捕获顺序
ZeroDivisionErrorArithmeticError第 1 位
OverflowErrorArithmeticError第 2 位
ArithmeticErrorException第 3 位
ExceptionBaseException最后(第 4 位)

如果你把 ArithmeticError 放在 ZeroDivisionError 前面,所有算数相关异常都会被它截胡,具体的子类就永远没机会了。

切记:永远不要写 except:(不指定异常类型)——这会捕获 SystemExit、KeyboardInterrupt 等严重异常,导致程序无法正常退出。真要兜底,用 except Exception 就够了。

实践建议:如何排好顺序

  1. 先列所有可能的具体异常:写代码前把会抛出的异常类型列出来(比如 ValueError、KeyError、ConnectionError)。
  2. 按继承层次从底向上排:最具体的子类放最上面,逐层往上,最后是 Exception。
  3. 同一层次的随意排:比如 ValueError 和 TypeError 没有继承关系,顺序无所谓。
  4. 记得加日志:每个 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 块的顺序就是一道「异常拦截链」。你安排得越精细,代码越健壮;随意排列,就是给自己埋雷。嗯,今天就聊到这,动手试试吧。