Python 编程

异常处理:让脚本不会一崩到底

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」是灾难写法?什么情况下应该捕获异常,什么情况下应该让它抛出去?

登录 后可看答案