第 8 章 异常处理与调试
8.1 异常是什么
程序运行中的错误叫异常(Exception)。未捕获的异常会终止程序并打印回溯(traceback):
学会读 traceback:从下往上看——最后一行是异常类型和消息,往上每一层 File "..." 是调用经过的文件与行号,最先定位到最后一行自己代码的行号。
常见内置异常:
| 异常 | 触发场景 |
|---|---|
NameError | 使用未定义的变量 |
TypeError | 类型不匹配的操作("1" + 1) |
ValueError | 类型对但值非法(int("abc")) |
IndexError / KeyError | 索引越界 / 字典键不存在 |
FileNotFoundError | 文件不存在 |
ZeroDivisionError | 除零 |
AttributeError | 对象没有该属性/方法 |
ImportError / ModuleNotFoundError | 导入失败 |
完整层级可用 help("exceptions") 查看;所有异常都继承自 BaseException(业务异常继承 Exception)。
8.2 try / except:捕获与处理
工作方式:执行 try 块,一旦抛出异常,立即跳到第一个匹配的 except 块。
捕获多个异常
不要裸 except:(会连 Ctrl-C 的 KeyboardInterrupt 一起吞掉),最低限度写 except Exception。
不要吞异常——捕获后什么都不做(空的 except 块)会让 bug 无声消失:
else 与 finally
执行顺序:try 正常 → else → finally;try 抛异常 → 匹配的 except → finally。finally 一定会执行(即使有 return/未捕获异常)。
8.3 主动抛出:raise
在 except 中重新抛出(记录后不吞):
raise NewError(...) from e 表示"由 e 引起",保留因果链。
8.4 自定义异常
业务项目里定义自己的异常层级:
命名以 Error 结尾,构造消息写清楚"出了什么问题、关键值是什么"。
8.5 断言 assert
注意:python -O 运行时 assert 全部被忽略,因此绝不能用 assert 做输入校验或业务判断——它只用于开发期自检(不变式)。运行期校验用 raise。
8.6 上下文管理器 with:资源管理的正解
文件、锁、网络连接等资源用 with 管理,离开块时自动清理(即使中途抛异常):
原理是魔术方法 __enter__ / __exit__(见第 6 章)。自定义可复用:
8.7 调试手段
① print 大法(快速但原始)
② logging(正规军)
分级:DEBUG < INFO < WARNING < ERROR < CRITICAL。设 level 控制输出粒度。
③ 断点调试器 pdb
进入 pdb 后常用命令:n(下一行)s(步入)c(继续)p x(打印变量)q(退出)。VS Code / PyCharm 的图形断点本质相同,日常开发更推荐 IDE 断点。
④ 静态检查
8.8 EAFP 与 LBYL
Python 社区两种风格:
Python 偏爱 EAFP——异常开销在"不发生"时几乎为零,且没有竞态问题。但两者都对:频繁预期的失败(如解析用户输入)用 try;简单判断用 in/get。
8.9 本章小结
traceback 从下往上读;先认全常见异常类型;
捕获要精确,永远别静默吞异常;
finally必执行,资源用with;raise主动抛错、裸raise重抛;业务异常自建层级;assert 只做开发期自检;生产校验用 raise;
调试三件套:print(f"{x=}") → logging → 断点。
下一章:文件与数据格式。