程序跑着跑着会撞上【预料之外】:除零、文件不存在、网络断线——异常是出错时的【警报弹起】,异常处理(try/except)是提前铺好的【安全气垫】:报警了不崩车,接住、处理、继续跑。
4-1~4-7 都在【一切正常】的假设下写代码,这一篇补上【不正常时怎么办】。回收两处旧地:数据库课第四章的事务(出错全回滚)是异常处理的业务版;C++ 课第十四章的 try/catch 是同一机制的另一门语法。
一、异常:出错的警报
错误在 Python 里不是【返回个错误码】而是弹起一个异常对象——不接住就一路往上冒,冒到顶程序终止:
print(10 // 0)
# ZeroDivisionError: division by zero
常见异常族(每个都是一种类型,可分别接住):
| 异常 | 什么时候弹 |
|---|---|
| ZeroDivisionError | 除零 |
| FileNotFoundError | 打开不存在的文件 |
| ValueError | 值类型对但内容不行(int("abc")) |
| TypeError | 类型不匹配("a" + 1) |
| KeyError | 字典里没有这个键 |
| IndexError | 下标越界 |
二、try/except:铺好安全气垫
try:
a = int(input("被除数: "))
b = int(input("除数: "))
print("商 =", a // b)
except ZeroDivisionError:
print("除数不能是 0")
except ValueError:
print("请输入整数")
else:
print("计算完成") # 没出异常才走
finally:
print("(本次计算结束)") # 无论成败都走——收尾专用
四块的分工:try 装【可能出事的代码】;except 按类型分别接住(精准处理);else 装【没出事才做的事】;finally 装无论如何都要做的收尾(关文件、还资源——回收树莓派课第十五章 finally 停车的同款纪律:不写 finally,车会带着最后的速度一直跑)。
异常也是对象:except ZeroDivisionError as e: 把它接住查看,e 里有错误信息——日志里常打它。
三、主动报警:raise
自己写的函数发现参数不对,也可以主动弹异常让调用方处理:
def divide(a, b):
if b == 0:
raise ValueError("除数不能为 0") # 主动报警
return a / b
try:
print(divide(10, 0))
except ValueError as e:
print("接住:", e) # 接住: 除数不能为 0
这比返回 -1 之类的【错误码】强:错误码会被调用方忘查,异常不接住就崩——想装看不见都难。
四、动手 Demo:除零与文件双保险
def safe_div(a, b):
try:
return a // b
except ZeroDivisionError:
return None # 或 raise 更明确——按业务定
print(safe_div(10, 3), safe_div(10, 0)) # 3 None
try:
open("不存在的文件.txt", encoding="utf-8")
except FileNotFoundError as e:
print("接住:", type(e).__name__) # 接住: FileNotFoundError
finally:
print("收尾动作照跑")
看到什么算成功:3 None、接住: FileNotFoundError、收尾动作照跑。
读结果:第一行展示【接住后自行决定返回什么】;后两行展示【按类型精准接住】与【finally 永远执行】——气垫铺在哪,程序就崩在哪之外。
五、30 秒实验
跑 int("abc") 看弹出的 ValueError 全文;再用 try: int("abc")\nexcept ValueError as e: print(e) 把它接住——同一次出错,一次崩、一次优雅。30 秒体会异常的两面。
六、常见误区
- 【裸 except 一网打尽最省事】——危险。
except:会连 KeyboardInterrupt、拼写错误的变量名都吞掉,真正的 bug 被气垫藏住——按类型接,最次也接 Exception。 - 【try 包得越多越安全】——反了。try 块一大,报错时不知道哪行出的事;只包可能出事的那几行,定位才快。
- 【拿异常当流程控制】——错。循环判断该用 if(可预期的正常分支),异常是【预料之外】的警报——用异常做常规跳转既慢又难读。
自测三问(点开对答案)
1. **try/except/else/finally 四块各干什么?** 答:try 装可能出事的代码;except 按类型接住处理;else 在没出事时执行;finally 无论成败都执行(收尾资源专用)。 2. **为什么 raise 比返回错误码好?** 答:异常不接住就崩,调用方想忽略都难;错误码极易被忘查,静默吞掉后患无穷。 3. **为什么不要写裸 except?** 答:它会吞掉所有异常(包括真正要暴露的 bug 与中断信号),错误被气垫藏住、无从排查——按类型精准接住。- ☐ 下一节(4-9 迭代器)
相关笔记
- 函数 —— raise 常住在函数的参数检查里
- 数据库 4-14 · 事务 —— 出错全回滚的业务版
- C++ 14.2 · 异常处理 —— try/catch 的 C++ 形态
- ⌨️ 编程原理 · 索引 —— 全课路线图