课程库 › AI 主线 › Python 进阶语法 › 第 5 课
错误与异常处理 —— try/except:程序的安全气囊
普通课约 60 分钟
本课导读
一路走来你已经见过不少红字:KeyError、IndexError、TypeError……阶段 0 的态度是"读懂它、修好它"。本课升级认知:报错不是敌人,是信使——先学会系统地读 traceback,再学 try/except 给程序装安全气囊:该扛住的扛住(用户输错一个数,程序不能整个瘫掉),该暴露的暴露(数据错了就要响亮地报出来)。预计 60 分钟。
一、读懂 traceback:从最后一行往上看
先故意制造一次事故,看清报错信息的结构:
运行后你会看到一段 traceback(调用轨迹)。读法口诀:先看最后一行,再往上找自己的代码。
- 最后一行是"诊断书":
KeyError: '摩卡'——错误类型 + 具体线索 - 往上的每一层是"案发路线":错误发生在哪一行、这行又是被谁调用的
- 排查时优先盯自己写的那几行,库内部的层级多数时候不用管
常见错误类型速查(都是老熟人了,认个全家福):
| 报错 | 人话翻译 | 典型场景 |
|---|---|---|
KeyError | 字典里没这个键 | menu["摩卡"] |
IndexError | 下标超出范围 | nums[99] |
TypeError | 类型对不上 | "3" + 5 |
ValueError | 类型对但值不合法 | int("abc") |
NameError | 变量根本不存在 | 拼错变量名 |
ZeroDivisionError | 除以零 | total / 0 |
二、try/except:安全气囊
有些错误你预料得到但拦不住——最典型的是用户输入。运营小林写了个折扣计算器,用户输入"八折"而不是数字,程序当场崩溃。try/except 出场:
结构拆解:
try:块里放可能出事的代码——出事瞬间跳出,直奔 exceptexcept 错误类型:接住指定类型的错误,执行善后代码;程序不崩,继续往下走- 可以叠多个 except,不同错误不同处理——就像医院分诊
提示
类比记忆:try/except 是汽车安全气囊——正常行驶时完全无感(try 里没出错就直接跳过 except),撞击瞬间弹出保护(错误被接住,程序不散架)。注意:气囊是为"预料得到的意外"设计的,不是让你闭眼开车。
三、气囊的正确姿势与三个坑
坑一:裸 except 一把梭。
try:
result = do_something(data)
except: # 什么错都吞掉 —— 千万别这么写
print("出错了")
except: 不写类型会吞掉一切错误——包括你代码里真正的 bug(比如变量名拼错的 NameError)。bug 被无声吞掉,比崩溃可怕一百倍。永远写明错误类型。
坑二:try 的范围过大。 只把危险的那一两行放进 try——范围越小,你越清楚接住的是什么错。
坑三:能预防就不要抢救。 上一课的 .get()、in 探路都是预防;try/except 是抢救。查字典这类能提前检查的,预防更清晰;用户输入、文件读取这类"检查了也可能出意外"的,才用气囊。
注意分工:类型转换失败交给 except(预料外),数值范围检查用 if(预料内)——气囊和方向盘各干各的活。
四、主动报错:raise 一嗓子
反过来,有时你希望程序响亮地报错而不是默默容忍。函数收到不合法的参数时,可以主动 raise:
raise 错误类型("说明文字") 主动抛出错误。看起来像自找麻烦,其实是把事故拦在案发第一现场——不然一个错误的折扣率会一路算下去,最后在几百行外炸出个莫名其妙的结果,排查半天。
说明
一句话总结错误哲学:对外来的意外要宽容(try/except 接住),对内部的错误要严格(让它响亮地炸出来,甚至主动 raise)。这个分寸感,是新手到熟手的重要一跃。
✍️ 练习
练习 1:报错侦探
不运行,先说出下面每一行各会引发哪种错误,再逐行取消注释验证。
✅ 参考答案
依次是:IndexError(下标 5 超范围)、KeyError(没有 age 键)、ValueError(类型对但值不合法)、TypeError(列表和数字不能相加)。能秒答这四个,traceback 对你就没有威慑力了。
练习 2:健壮的平均分
avg 函数在列表为空时会崩(除以零)。用 try/except 修好它:空列表时返回 0。
💡 提示
把除法那行放进 try,except 接 ZeroDivisionError 返回 0。(也可以用 if 预防——两种都写写,体会"预防 vs 抢救"。)
✅ 参考答案
def avg(scores):
try:
return sum(scores) / len(scores)
except ZeroDivisionError:
return 0
# 预防式写法(本例中更推荐——空列表是能提前检查的):
def avg2(scores):
if len(scores) == 0:
return 0
return sum(scores) / len(scores)
print(avg([80, 90, 100]), avg([]))
print(avg2([80, 90, 100]), avg2([]))
练习 3:批量转换幸存者(挑战)
一批用户填的年龄有好有坏。写代码把能转成整数的收进 valid 列表,转不了的收进 rejected 列表,最后打印两个列表。程序全程不能崩。
💡 提示
for 遍历,循环体里 try 转换 + append 到 valid,except ValueError 时 append 到 rejected。气囊装在循环里面,一个坏数据才不会中断整批处理。
✅ 参考答案
raw_ages = ["25", "abc", "31", "", "40岁", "18"]
valid = []
rejected = []
for raw in raw_ages:
try:
valid.append(int(raw))
except ValueError:
rejected.append(raw)
print(f"有效:{valid}")
print(f"待人工处理:{rejected}")
这个"逐条 try、坏的隔离、好的继续"模式,是数据清洗的标准动作——pandas 课里你会重逢它。
📝 随堂测验
1. 看 traceback 的第一步是?
2. int("abc") 会引发哪种错误?
3. 为什么不该写不带类型的裸 except: ?
4. “查字典前先用 in 探路”和“try/except 包住取值”,选择的原则是?
5. 函数里写 raise ValueError("折扣率不合法") 的目的是?
本课小结
- traceback 读法:最后一行看诊断,往上找自己的代码;六大常见错误类型认全
try/except 错误类型:安全气囊——接预料得到的意外;永远写明类型,try 范围要小- 能预防(in/.get/if 检查)就不抢救;用户输入、文件读取才是气囊的主场
raise:对不合法的输入主动报错——错误越早暴露越好修
下一课聊聊"代码的仪表盘"——命名、注释与 PEP8:让三个月后的你还能看懂今天的代码。
划选正文任意文字可高亮、批注或加入复习卡
讨论
载入中…