第6章 6.1 try/except/finally 异常处理
🎯 开场:程序也会"生病"
上一章我们学会了用 JSON/CSV/YAML 这些格式存数据、取数据,手里终于有了"数据瑞士军刀"。但你有没有遇到过这种情况——
# 你的代码
data = open("不存在的文件.txt", "r", encoding="utf-8")
content = data.read()
print(content)
运行结果:
FileNotFoundError: [Errno 2] No such file or directory: '不存在的文件.txt'
程序直接崩溃,红字一片。这感觉就像你信心满满去超市买菜,结果到了发现超市倒闭了——白跑一趟。
痛点来了:
1. 用户输入了一个不存在的文件路径,程序直接炸了
2. 网络请求超时,程序直接挂掉
3. 读取的数据格式不对,整段代码都废掉
这章学完,你能:给程序穿上"防护服",让它在出错时不是崩溃而是优雅地处理。就像超市倒闭了你还能去隔壁菜市场,而不是站在原地干瞪眼。
🧱 基础:异常处理的三个金刚
异常处理是什么?
类比时间: 就像开车系安全带。正常情况下你不需要它,但万一出事,它能保命。异常处理就是程序的"安全带"。
1. try...except:捕获异常
try:
# 尝试执行这段代码
result = 10 / 0 # 这行会出错
except ZeroDivisionError:
# 如果出错了,执行这段
print("糟糕,除数不能是0!")
输出:
糟糕,除数不能是0!
说白了: try 就是"试试这段代码能不能跑",except 就是"跑砸了怎么办"。
2. except 多种异常:分别处理
try:
# 尝试执行
num = int(input("输入一个数字:")) # 用户可能输入"abc"
result = 100 / num
except ValueError:
print("喂,我说的是数字!")
except ZeroDivisionError:
print("除数不能是0,知道吗?")
用户输入 abc:
输入一个数字:abc
喂,我说的是数字!
用户输入 0:
输入一个数字:0
除数不能是0,知道吗?
注意! 异常类型是有层级的,比如 FileNotFoundError 是 OSError 的子类。如果你写成:
except OSError:
print("文件操作出错")
except FileNotFoundError:
print("文件找不到") # 这行永远执行不到!
第二个 except 永远不会被触发,因为 FileNotFoundError 已经被前面的 OSError 捕获了。
3. except 带变量:拿到错误信息
try:
with open("data.json", "r", encoding="utf-8") as f:
data = json.load(f)
except FileNotFoundError as e:
print(f"文件不存在:{e}")
except json.JSONDecodeError as e:
print(f"JSON格式不对:{e}")
as e 的意思是"把错误对象取个名字叫 e",这样你就能看到具体是什么错。
4. else:不出错才执行
try:
result = 10 / 2
except ZeroDivisionError:
print("出错了")
else:
print(f"计算成功,结果是 {result}")
输出:
计算成功,结果是 5.0
什么时候用? 当你想区分"成功执行"和"异常处理"两种情况时。

5. finally:无论如何都执行
try:
file = open("test.txt", "r", encoding="utf-8")
content = file.read()
except FileNotFoundError:
print("文件不存在")
finally:
# 不管出没出错,这段都会执行
print("无论怎样,程序跑完了")
重点! finally 最常用的场景是关闭文件、释放连接等"清理工作"。
# 推荐写法:with 自动关闭文件,但 finally 适合更通用的场景
try:
result = 10 / 0
except ZeroDivisionError:
print("出错了")
finally:
print("清理资源...")
6. raise:主动抛出异常
有时候不是你踩坑了,而是你主动告诉别人"这里有问题"。
def divide(a, b):
if b == 0:
raise ValueError("除数不能是0!")
return a / b
try:
result = divide(10, 0)
except ValueError as e:
print(f"捕获到错误:{e}")
输出:
捕获到错误:除数不能是0!
类比: raise 就像是机场安检,发现可疑物品直接拦下来,而不是让飞机起飞后才发现问题。
🔥 实战:3 个小项目
项目 1:计算器(5 分钟)
def safe_divide(a, b):
"""安全的除法运算"""
try:
result = a / b
except ZeroDivisionError:
return "错误:除数不能是0"
except TypeError:
return "错误:请输入数字"
else:
return f"结果:{result}"
# 测试
print(safe_divide(10, 2))
print(safe_divide(10, 0))
print(safe_divide(10, "2"))
输出:
结果:5.0
错误:除数不能是0
错误:请输入数字
一句话解释: 用 try/except 把除法包起来,出了问题返回错误提示而不是崩溃。
项目 2:读取 CSV 文件并计算(15 分钟)
从 CSV 文件读取销售数据,计算总额并处理各种错误。
import csv
def calculate_sales(file_path):
"""读取CSV文件并计算销售总额"""
total = 0
errors = []
try:
with open(file_path, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row_num, row in enumerate(reader, 1):
try:
# 尝试把数量和单价转成数字
quantity = float(row["数量"])
price = float(row["单价"])
total += quantity * price
except KeyError as e:
errors.append(f"第{row_num}行:缺少字段 {e}")
except ValueError as e:
errors.append(f"第{row_num}行:数据格式错误 {e}")
except FileNotFoundError:
return f"错误:文件 '{file_path}' 不存在"
except PermissionError:
return f"错误:没有权限读取 '{file_path}'"
# 打印错误信息
if errors:
print("处理过程中的错误:")
for err in errors:
print(f" - {err}")
return f"销售总额:{total:.2f} 元"
# 先创建一个测试文件
with open("sales.csv", "w", encoding="utf-8") as f:
f.write("商品,数量,单价\n")
f.write("苹果,10,3.5\n")
f.write("香蕉,5,2.0\n")
f.write("西瓜,错误数据,8.0\n") # 这行会出错
# 运行
print(calculate_sales("sales.csv"))
输出:
处理过程中的错误:
- 第3行:数据格式错误 could not convert string to float: '错误数据'
销售总额:45.00 元
一句话解释: 用嵌套的 try/except 处理行级别的错误,同时用外层 try/except 处理文件级别的错误。

项目 3:命令行待办清单(15 分钟)
一个带异常处理的待办清单工具,支持从文件读取、保存到文件。
import json
import os
TODO_FILE = "todos.json"
def load_todos():
"""加载待办清单"""
try:
with open(TODO_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return [] # 文件不存在,返回空清单
except json.JSONDecodeError:
print("警告:文件损坏,将创建新清单")
return []
def save_todos(todos):
"""保存待办清单"""
try:
with open(TODO_FILE, "w", encoding="utf-8") as f:
json.dump(todos, f, ensure_ascii=False, indent=2)
except PermissionError:
print("错误:没有写入权限")
return False
return True
def add_todo(todos, task):
"""添加待办事项"""
if not task or not task.strip():
raise ValueError("任务不能为空")
todos.append({"task": task.strip(), "done": False})
return todos
def complete_todo(todos, index):
"""标记完成"""
try:
todos[index]["done"] = True
except IndexError:
raise IndexError(f"任务 {index} 不存在")
def list_todos(todos):
"""显示待办清单"""
if not todos:
print("清单是空的,添加一个吧!")
return
for i, todo in enumerate(todos):
status = "✓" if todo["done"] else "○"
print(f"{i}. [{status}] {todo['task']}")
def main():
todos = load_todos()
print("=== 待办清单工具 ===")
print("命令:add <任务> / done <编号> / list / quit")
while True:
try:
cmd = input("\n请输入命令:").strip()
if not cmd:
continue
if cmd == "quit":
if save_todos(todos):
print("再见!清单已保存")
break
elif cmd == "list":
list_todos(todos)
elif cmd.startswith("add "):
task = cmd[4:]
todos = add_todo(todos, task)
print(f"已添加:{task}")
elif cmd.startswith("done "):
index = int(cmd[5:]) - 1 # 用户看到的是1开始的编号
complete_todo(todos, index)
print(f"已完成:{todos[index]['task']}")
else:
print("未知命令,有效命令:add, done, list, quit")
except ValueError as e:
print(f"输入错误:{e}")
except IndexError as e:
print(f"编号错误:{e}")
if __name__ == "__main__":
main()
示例运行:
=== 待办清单工具 ===
命令:add <任务> / done <编号> / list / quit
请输入命令:add 买牛奶
已添加:买牛奶
请输入命令:add 写周报
已添加:写周报
请输入命令:list
0. [○] 买牛奶
1. [○] 写周报
请输入命令:done 1
已完成:买牛奶
请输入命令:list
0. [✓] 买牛奶
1. [○] 写周报
请输入命令:quit
再见!清单已保存
一句话解释: 用异常处理保护文件读写和用户输入,让程序在各种出错情况下都能优雅应对。
💪 进阶:常见坑 + 小贴士
坑 1:裸 except 捕获所有异常
# ❌ 错误:这样会隐藏所有错误,包括 KeyboardInterrupt
try:
result = 10 / 0
except:
print("出错了")
# ✅ 正确:明确指定要捕获的异常类型
try:
result = 10 / 0
except ZeroDivisionError:
print("出错了")
坑 2:except 顺序写错
# ❌ 错误:父类在前,子类永远捕获不到
try:
result = 10 / 0
except Exception:
print("捕获了所有异常")
except ZeroDivisionError:
print("这段永远不会执行")
# ✅ 正确:子类在前,父类在后
try:
result = 10 / 0
except ZeroDivisionError:
print("专门处理除零")
except Exception:
print("处理其他异常")
坑 3:try/except/else/finally 混用时的执行顺序
try:
print("1. try 开始")
# raise ValueError("故意的")
print("2. try 结束")
except:
print("3. except 执行")
else:
print("4. else 执行(仅在try成功时)")
finally:
print("5. finally 总是执行")
正常情况输出:
1. try 开始
2. try 结束
4. else 执行(仅在try成功时)
5. finally 总是执行
有异常时输出:
1. try 开始
3. except 执行
5. finally 总是执行
坑 4:异常被吞掉
# ❌ 错误:捕获后什么都没做
try:
result = 10 / 0
except ZeroDivisionError:
pass # 悄悄忽略错误
# ✅ 正确:至少记录日志
try:
result = 10 / 0
except ZeroDivisionError:
print("发生除零错误,已记录")
# 或者记录到日志文件
坑 5:文件操作没用 with
# ❌ 错误:忘记关闭文件
try:
file = open("data.txt", "r")
content = file.read()
except:
print("出错了")
# 如果这里出错,file 永远不会关闭
# ✅ 正确:用 with 自动管理
try:
with open("data.txt", "r") as file:
content = file.read()
except:
print("出错了")
# with 会自动关闭文件,即使出错也不怕
调试技巧:traceback 模块
import traceback
try:
result = 10 / 0
except Exception:
# 打印完整的错误堆栈
traceback.print_exc()
# 或者获取字符串
error_info = traceback.format_exc()
print(f"错误详情:{error_info}")
✏️ 练习题
练习 1(2 分钟):抄改计算器
- 输入:safe_divide(20, 4)
- 预期输出:结果:5.0
- 提示:直接调用项目1的函数
练习 2(2 分钟):加个判断
- 在项目1的 safe_divide 中,如果结果大于10,返回 "结果大于10"
- 预期输出:safe_divide(15, 1) → "结果大于10"
- 提示:在 else 分支里加 if 判断
练习 3(3 分钟):读取新CSV
- 用项目2的方法处理以下数据:
名称,数量,单价
书本,3,45.5
铅笔,10,2.0
- 预期输出:
销售总额:156.50 元 - 提示:创建新CSV文件,调用
calculate_sales
练习 4(3 分钟):串起两个项目
- 把项目2的CSV计算功能和项目3的保存功能结合
- 实现:从CSV读取数据,计算总额,保存结果到新文件
- 提示:两个函数的返回值都是字符串,可以直接写文件
练习 5(5 分钟):分析报错
- 运行以下代码,分析为什么会报错:
try:
data = {"name": "小明", "age": 20}
print(data["score"])
except KeyError:
print("KeyError")
except Exception:
print("Exception")
- 预期输出:分析原因
- 提示:看最后执行的是哪个 except
作业:做一个「数据验证工具」
做一个命令行工具,验证用户输入的数据是否符合规则:
- 需求:提示用户输入姓名、年龄、邮箱,验证通过后保存到JSON文件
- 功能点:
1. 用 try/except 处理各种输入错误
2. 用 raise 主动抛出验证失败
3. 用 finally 确保程序结束时打印"验证完成"
4. 数据保存用 JSON 格式 - 加分项:
1. 支持多次输入直到验证通过
2. 把验证规则写成单独的函数 - 验收标准:输入错误时提示友好,不崩溃;正确数据能保存到文件
- 提交方式:评论区贴代码
📚 总结
这章学了3个核心点:
1. try/except 是程序的"安全带",让崩溃变成友好提示
2. else 处理成功情况,finally 处理清理工作
3. raise 可以主动告诉别人"这里有问题"
延伸学习:
- 官方文档:https://docs.python.org/3/tutorial/errors.html
- 书籍:《Python编程:从入门到实践》第9章
- 视频:B站「Python异常处理」相关教程
互动钩子: 你在写代码时遇到过什么奇葩崩溃?是用 try/except 解决的还是在评论区聊聊,老粉优先回复!
下一章我们要聊的是自定义异常——Python 内置的异常不够用时,怎么造自己的"专属异常"?敬请期待!

评论(0)