异常处理:让脚本不会一崩到底
约 14 分钟
第二章讲过「看懂报错」。这一节讲更进一步的事:主动处理异常,让程序在出错时也能体面地继续或退出。
基本结构
try:
value = int(user_input)
except ValueError:
print("请输入一个整数")
完整形态有四块:
try:
f = open("data.csv")
data = f.read()
except FileNotFoundError:
print("文件不存在")
except PermissionError:
print("没有读取权限")
else:
print("读取成功") # 没出异常才执行
finally:
f.close() # 不管有没有异常都执行
| 块 | 什么时候执行 |
|---|---|
try |
总是先执行 |
except |
出了对应异常时 |
else |
没出异常时 |
finally |
总是执行(常用来释放资源) |
三条重要原则
① 不要裸 except
# ❌ 灾难写法
try:
do_something()
except:
pass
这会吞掉所有异常,包括你按 Ctrl+C 想中断程序(KeyboardInterrupt)、包括拼写错误导致的 NameError。问题被完全掩盖,调试时会疯掉。
正确做法:只捕获你知道怎么处理的异常。
except ValueError as e:
print(f"数据格式有问题:{e}")
② 捕获得越具体越好
except (FileNotFoundError, PermissionError) as e: # 明确列出
③ 处理不了就别捕获
如果你捕获了异常却只能打印一句「出错了」然后继续跑,通常还不如让它崩掉——崩溃会给你完整的调用栈,而「出错了」什么信息都没有。
能恢复才捕获,不能恢复就让它抛出去。
用 with 自动管理资源
# 老写法:容易忘记 close
f = open("data.csv")
data = f.read()
f.close()
# 推荐写法:离开代码块自动关闭,哪怕中间抛异常
with open("data.csv", encoding="utf-8") as f:
data = f.read()
with 应该成为你的默认写法——文件、数据库连接、网络请求都支持它。
主动抛异常
def set_age(age):
if age < 0:
raise ValueError(f"年龄不能是负数:{age}")
return age
为什么要主动抛:尽早失败,比带着错误数据往下跑好得多。 一个负数年龄如果不拦,可能在几十行之后才引发一个莫名其妙的错误,那时候你根本不知道问题出在哪。
写脚本时的实用模式
处理一批文件时,希望单个失败不影响整批:
results, failed = [], []
for path in files:
try:
results.append(process(path))
except Exception as e:
failed.append((path, str(e))) # 记下来,继续跑
print(f"成功 {len(results)},失败 {len(failed)}")
for path, err in failed:
print(f" {path}: {err}")
关键是把失败记录下来并在最后汇总,而不是静默跳过。静默跳过的数据处理脚本,是数据分析里最危险的东西之一——你会以为处理了全部数据,实际只处理了一半。
练习:写一个函数读取 CSV 并返回 DataFrame,要求分别处理「文件不存在」和「编码错误」两种情况,并给出清晰的提示。
小纸条
为什么「裸 except」是灾难写法?什么情况下应该捕获异常,什么情况下应该让它抛出去?
登录 后可看答案