DAY29 异常处理
知识点回顾:
- 异常处理机制
- debug 过程中的各类报错
- try-except 机制
- try-except-else-finally #/LEU
Python 异常处理完全指南(零基础友好)
今天咱们从「什么是异常」开始,一步步拆解 4 个核心知识点,全程用「生活例子 + 简单代码 + 步骤拆解」,保证你能看懂、会用!
一、先搞懂:什么是 “异常”?
生活类比
你去买奶茶:
- 正常情况:付钱 → 拿到奶茶(程序正常运行)
- 异常情况:没带钱、奶茶卖完了(程序出错,直接 “崩溃”)
代码演示(没处理异常时)
# 需求:计算 10 除以一个数
num = int(input("请输入一个除数:"))
result = 10 / num # 这里可能出错!
print(f"结果是:{result}")

结论:异常就是程序运行中遇到的 “意外状况”,不处理的话程序会直接停止,这就是我们要学「异常处理」的原因 —— 让程序出错时不崩溃,还能优雅处理!
二、知识点 1:异常处理机制的核心逻辑
异常处理的本质是:“提前预判可能出错的地方,出错后执行备用方案,让程序继续跑”
核心流程(3 步走)

关键目标
- 程序不崩溃(最核心)
- 给出友好提示(比如 “除数不能为 0”,而不是一堆看不懂的报错)
- 记录错误信息(方便后续 debug)
三、知识点 2:debug 过程中的各类常见报错
新手最怕 “报错”,但报错其实是 “提示”—— 告诉你「哪里错了、为什么错」。咱们先认识 6 种最常见的报错,记住 “场景 + 报错关键词”,以后看到就不怕!
报错解读技巧
所有报错都遵循:错误类型: 错误原因(行号会标红,直接看标红行!)比如 ZeroDivisionError: division by zero:
- 错误类型:
ZeroDivisionError(零除错误) - 错误原因:
division by zero(用 0 做除数了)
6 种常见报错(场景 + 代码 + 解决)
| 错误类型 | 触发场景 | 代码示例 | 报错信息 | 解决方法 |
|---|---|---|---|---|
| SyntaxError | 语法错误(代码写得不对) | print("Hello") 漏写括号 |
SyntaxError: invalid syntax |
检查标红行语法(括号、冒号、缩进) |
| ZeroDivisionError | 用 0 做除数 | 10 / 0 |
ZeroDivisionError: division by zero |
避免除数为 0 |
| TypeError | 数据类型不匹配 | 10 + "2"(数字 + 字符串) |
TypeError: unsupported operand type(s) for +: 'int' and 'str' |
统一数据类型(比如 str(10) + "2") |
| IndexError | 列表 / 字符串索引越界 | list1 = [1,2]; list1[2] |
IndexError: list index out of range |
索引从 0 开始,别超列表长度 |
| KeyError | 字典没有对应的键 | dict1 = {"name":"小明"}; dict1["age"] |
KeyError: 'age' |
先判断键是否存在,再取值 |
| ValueError | 数据值不合法 | int("abc") |
ValueError: invalid literal for int() with base 10: 'abc' |
确保输入值符合要求(比如输入数字) |
小练习
- 运行
print("Hello"→ 会报什么错?怎么改? - 运行
[1,2,3][3]→ 会报什么错?怎么改?(答案在最后,先往下学~)
四、知识点 3:try-except 基础机制(核心!)
try-except 是 Python 处理异常的 “基础工具”,作用是:“监控可能出错的代码,出错后执行备用方案”
1. 语法结构(固定格式!)
try:
# 可能会出错的代码(重点监控区)
代码1
代码2
except 错误类型1:
# 当“错误类型1”发生时,执行这里的备用方案
备用代码1
except 错误类型2:
# 当“错误类型2”发生时,执行这里的备用方案
备用代码2
# (可以加多个 except,捕获多种错误)
2. 执行流程(关键!)

3. 代码示例(分场景演示)
场景 1:捕获单个异常(比如零除错误)
try:
num = int(input("请输入一个除数:"))
result = 10 / num # 可能触发 ZeroDivisionError
print(f"计算结果:{result}") # 没出错才会执行这行
except ZeroDivisionError:
# 只有当“零除错误”发生时,才执行这里
print("出错啦!除数不能为 0 哦~")
# 不管有没有出错,这行都会执行(程序不崩溃)
print("程序结束,感谢使用!")
- 测试输入
0:输出出错啦!除数不能为 0 哦~→程序结束,感谢使用!(不崩溃) - 测试输入
2:输出计算结果:5.0→程序结束,感谢使用!(正常运行)
场景 2:捕获多个异常(比如零除 + 值错误)
try:
num = int(input("请输入一个除数:")) # 可能触发 ValueError(输入非数字)
result = 10 / num # 可能触发 ZeroDivisionError(输入 0)
print(f"计算结果:{result}")
except ZeroDivisionError:
print("出错啦!除数不能为 0 哦~")
except ValueError:
print("出错啦!请输入合法的整数哦~")
print("程序结束,感谢使用!")
- 测试输入
abc:输出出错啦!请输入合法的整数哦~ - 测试输入
0:输出出错啦!除数不能为 0 哦~ - 测试输入
5:输出计算结果:2.0
场景 3:捕获所有异常(不推荐,但新手应急用)
如果不知道会出现什么错误,可以用 except Exception:(捕获所有常见异常):
try:
num = int(input("请输入一个除数:"))
result = 10 / num
print(f"计算结果:{result}")
except Exception as e:
# e 是错误详情,会显示具体是什么错
print(f"出错啦!错误原因:{e}")
print("程序结束,感谢使用!")
- 输入
abc:输出出错啦!错误原因:invalid literal for int() with base 10: 'abc' - 输入
0:输出出错啦!错误原因:division by zero
4. 步骤拆解(写 try-except 的 3 个步骤)
- 找出「可能出错的代码」(比如用户输入、文件读取、网络请求)
- 把这些代码放进
try:缩进块里 - 预判可能出现的「错误类型」,在
except 错误类型:里写备用方案
五、知识点 4:try-except-else-finally 完整机制
在 try-except 基础上,增加 else(没出错时执行)和 finally(必执行),构成 “完整处理流程”。
1. 语法结构
try:
# 可能出错的代码(监控区)
代码1
except 错误类型:
# 出错时执行(备用方案)
代码2
else:
# 没出错时执行(正常流程的后续代码)
代码3
finally:
# 不管有没有出错,必执行(收尾工作)
代码4
2. 各部分作用(一句话总结)
| 关键字 | 执行条件 | 常用场景 |
|---|---|---|
| try | 必执行(监控错误) | 放可能出错的核心代码 |
| except | try 块出错且类型匹配时 | 错误处理(提示、日志) |
| else | try 块没出错时 | 正常流程的后续逻辑 |
| finally | 无条件执行(无论是否出错) | 资源释放(关文件、关连接) |
3. 执行流程(3 种场景全覆盖)



try-except-else-finally 3 种场景对比表
| 场景编号 | 场景描述 | 触发条件 | 各模块执行情况 | 程序最终状态 | 典型示例(文件读取场景) |
|---|---|---|---|---|---|
| 1 | try 块无错误 | try 块代码正常执行,未触发任何异常 | try:是except:否else:是finally:是 | 正常运行 | 文件存在且有权限,成功读取内容 |
| 2 | try 块出错且匹配 except | try 块触发异常,且异常类型和某个 except 匹配 | try:是(执行到出错行停止)except:是(匹配的那个)else:否finally:是 | 正常运行 | 文件不存在(触发 FileNotFoundError),匹配对应 except |
| 3 | try 块出错但不匹配 except | try 块触发异常,但无对应匹配的 except | try:是(执行到出错行停止)except:否else:否finally:是 | 崩溃并报错 | 文件存在但无权限(触发 PermissionError),未写对应 except |
关键备注(快速记)
- finally 永远执行:不管有没有错、错对不对得上,finally 的收尾工作必做;
- else 只在 “没出错” 时执行:相当于 “如果 try 没出问题,就接着做 else 里的事”;
- 程序崩不崩溃看 except:只有 “出错且没匹配到 except” 时,程序才会崩溃。
4. 代码示例(实战:文件读取)
文件读取是典型场景:可能出现 “文件不存在” 错误,读取后必须关闭文件(finally 作用)
try:
# 可能出错的代码:打开文件(文件可能不存在)
file = open("test.txt", "r", encoding="utf-8")
content = file.read() # 读取文件内容
except FileNotFoundError:
# 出错时:文件不存在的处理
print("错误:找不到 test.txt 文件!")
else:
# 没出错时:打印文件内容(正常流程)
print("文件内容:", content)
finally:
# 必执行:关闭文件(不管有没有出错,都要关)
try:
file.close() # 防止文件没打开成功(file 未定义),再加个小 try
print("文件已关闭")
except:
print("无需关闭文件(文件未打开)")
print("程序结束!")
场景 1:文件存在(没出错)
- 执行顺序:try → else → finally
- 输出:
文件内容:xxx→文件已关闭→程序结束!
场景 2:文件不存在(出错)
- 执行顺序:try → except → finally
- 输出:
错误:找不到 test.txt 文件!→无需关闭文件(文件未打开)→程序结束!
场景 3:文件存在但读取出错(比如权限问题)
- 执行顺序:try → (出错但不匹配 FileNotFoundError)→ finally → 程序崩溃
- 输出:
文件已关闭→ (崩溃报错)
5. 简化写法(else 的替代方案)
其实 else 里的代码,也可以直接写在 try 块的最后(没出错就会执行),但 else 让逻辑更清晰:
# 等价写法(else 简化)
try:
num = int(input("请输入除数:"))
result = 10 / num
except ZeroDivisionError:
print("除数不能为0")
except ValueError:
print("请输入整数")
else:
print(f"结果:{result}") # 没出错才执行
# 等价于(写在 try 里)
try:
num = int(input("请输入除数:"))
result = 10 / num
print(f"结果:{result}") # 没出错才会执行到这
except ZeroDivisionError:
print("除数不能为0")
except ValueError:
print("请输入整数")
6. finally 的核心作用(必须掌握)
finally 是 “必执行” 的,哪怕 try/except/else 里有 return(函数返回),也会先执行 finally:
def test():
try:
return 1 # 这里会返回,但先执行 finally
except:
return 2
else:
return 3
finally:
print("我一定会执行!")
test() # 输出:我一定会执行! → 返回 1
常用场景:关闭文件、关闭数据库连接、释放锁(这些操作必须做,否则会浪费资源)
六、小结 + 小练习
核心知识点小结
- 异常:程序运行中的意外错误,不处理会崩溃
- 常见报错:记住 6 种(SyntaxError/ZeroDivisionError/TypeError/IndexError/KeyError/ValueError)
- try-except:监控错误 + 处理错误(基础工具)
- try-except-else-finally:完整流程(else 正常执行,finally 必执行)
小练习(动手写代码!)
- 写一个程序:让用户输入两个整数,计算它们的和。处理 “输入非整数” 的错误,没出错则打印和,最后无论如何都打印 “计算结束”。
- 写一个程序:尝试访问列表
[1,2,3]的第 n 个元素(n 由用户输入),处理 “索引越界” 和 “输入非整数” 的错误,最后必打印 “程序结束”。
练习答案(参考)
1. 计算两数之和:
try:
a = int(input("请输入第一个整数:"))
b = int(input("请输入第二个整数:"))
sum_ab = a + b
except ValueError:
print("错误:请输入合法的整数!")
else:
print(f"两数之和:{sum_ab}")
finally:
print("计算结束")
2. 访问列表元素:
my_list = [1,2,3]
try:
n = int(input("请输入要访问的索引(0-2):"))
print(f"列表第 {n} 个元素是:{my_list[n]}")
except ValueError:
print("错误:请输入整数索引!")
except IndexError:
print(f"错误:索引越界!列表只有 3 个元素,索引是 0-2")
finally:
print("程序结束")
之前小练习的答案
print("Hello"→ 报SyntaxError(语法错误),原因是漏写右括号,修改为print("Hello")[1,2,3][3]→ 报IndexError(索引越界),原因是列表长度为 3,索引最大是 2,修改为[1,2,3][2]
除了try-except结构,还有哪些异常处理结构?
除了 try-except 基础结构,Python 还有 4 种常用的异常处理相关结构 —— 它们本质是 try-except 的 “补充 / 简化 / 扩展”,更适配不同实际场景(比如主动抛异常、简化资源管理、自定义业务错误)。
下面用「零基础能懂的作用 + 语法 + 代码示例 + 场景」拆解,和之前的知识点呼应,方便你串联记忆:
一、raise:主动抛出异常(“主动报警”)
作用:
当程序满足某个 “不合理条件” 时,主动触发异常(比如用户年龄输入负数、转账余额不足),让程序按预设逻辑处理(而不是等到后续代码崩溃)。
通俗类比:
你去银行转账,余额只剩 100 元,却要转 200 元 —— 银行系统不会等扣款时才出错,而是直接提示 “余额不足”(主动抛出异常)。
语法(2 种常用形式):
# 1. 抛出 Python 自带的异常(直接用现成的错误类型)
raise 错误类型("错误提示信息")
# 2. 重新抛出捕获到的异常(不改变原始错误,仅做额外处理)
try:
可能出错的代码
except 错误类型 as e:
# 做一些处理(比如记录日志)
raise # 重新抛出原异常,让上层代码处理
代码示例(实际场景:验证用户年龄)
def set_age(age):
# 主动检查:年龄不能小于0或大于120(不合理条件)
if age < 0 or age > 120:
# 主动抛出 ValueError,附带友好提示
raise ValueError(f"年龄 {age} 不合法!请输入 0-120 之间的数")
print(f"年龄设置为:{age}")
# 调用函数,用 try-except 捕获主动抛出的异常
try:
set_age(-5) # 输入不合理值
except ValueError as e:
print("出错啦:", e) # 输出:出错啦:年龄 -5 不合法!请输入 0-120 之间的数
关键用法:
- 用于 “业务逻辑校验”(比如参数合法性、数据有效性);
- 抛出的异常必须是
Exception类的子类(不能随便写个字符串)。
二、assert:断言(“调试时的快速校验”)
作用:
调试阶段快速验证 “某个条件必须成立”,如果不成立,直接抛出 AssertionError(断言错误)—— 相当于 “简化版的 raise”,只用于调试,生产环境会被禁用。
通俗类比:
你写作业时,默认 “作业本一定在书包里”(断言条件),如果没找到(条件不成立),直接提示 “作业本不见了”(抛出异常),方便你快速发现问题。
语法:
assert 条件表达式, "条件不成立时的提示信息"
代码示例(实际场景:调试函数参数)
def calculate_area(radius):
# 断言:半径必须大于0(调试时校验,避免后续计算出错)
assert radius > 0, "半径不能为负数或0!"
return 3.14 * radius * radius
# 调试时调用
try:
calculate_area(-2) # 违反断言条件
except AssertionError as e:
print("调试发现问题:", e) # 输出:调试发现问题:半径不能为负数或0!
关键注意:
- 生产环境中,用
python -O 脚本名.py运行(O 是大写),会忽略所有assert(所以不能用它做生产环境的校验); - 核心用途:调试时快速定位 “逻辑漏洞”(比如参数错误、数据异常)。
三、with 语句(上下文管理器):简化资源异常处理(“自动收尾”)
作用:
自动处理资源的 “打开 / 关闭” 和异常,本质是 try-except-finally 的 “简化写法”—— 不用手动写 finally 关闭文件、数据库连接等,避免资源泄露。
通俗类比:
你住酒店,不用自己操心 “开门 / 关门”:入住时前台给钥匙(自动打开资源),退房时前台收回钥匙(自动关闭资源),哪怕中途出问题(比如房间断电),也会自动处理收尾。
语法:
with 资源对象 as 变量名:
# 操作资源的代码(比如读文件、数据库查询)
# 缩进结束后,资源自动关闭(无论是否出错)
代码示例(对比之前的文件处理,突出简化)
# 之前的写法(用 try-except-finally 手动关文件)
file = None
try:
file = open("test.txt", "r")
print(file.read())
except FileNotFoundError:
print("文件不存在")
finally:
if file:
file.close()
# with 简化写法(自动处理异常+关闭文件)
try:
with open("test.txt", "r") as file:
print(file.read()) # 缩进内操作文件
except FileNotFoundError:
print("文件不存在")
# 缩进结束,file 自动关闭(哪怕读文件时出错也会关)
常用场景:
- 文件操作(
open())、数据库连接(pymysql.connect())、网络请求(部分库支持); - 核心优势:代码更简洁,不用手动处理
finally,避免遗漏资源关闭。
四、自定义异常类:处理业务专属异常(“定制化报警”)
作用:
Python 自带的异常(如 ValueError)无法满足具体业务场景(比如 “余额不足”“账号冻结”),这时可以自定义异常类,让异常处理更精准。
通俗类比:
银行系统除了 “系统故障”(自带异常),还有 “余额不足”“账号冻结”(自定义业务异常)—— 不同异常对应不同处理逻辑(比如余额不足提示充值,账号冻结提示联系客服)。
语法(3 步):
- 继承
Exception类(所有异常的父类); - 定义类名(见名知义,比如
InsufficientBalanceError); - (可选)添加自定义提示信息。
代码示例(实际场景:转账业务)
# 1. 自定义异常类(继承 Exception)
class InsufficientBalanceError(Exception):
# 可选:重写 __init__ 方法,自定义提示信息
def __init__(self, balance, amount):
# 调用父类构造方法,传递提示信息
super().__init__(f"余额不足!当前余额:{balance},需转账:{amount}")
# 2. 业务函数(转账)
def transfer(balance, amount):
if amount > balance:
# 抛出自定义异常
raise InsufficientBalanceError(balance, amount)
print(f"转账成功!剩余余额:{balance - amount}")
# 3. 捕获自定义异常
try:
transfer(100, 200) # 余额100,转账200(触发自定义异常)
except InsufficientBalanceError as e:
print("转账失败:", e) # 输出:转账失败:余额不足!当前余额:100,需转账:200
关键用法:
- 类名必须 “见名知义”(用 Error 结尾,比如
AccountFrozenError账号冻结异常); - 只用于 “业务专属异常”,通用异常(比如输入错误)用 Python 自带的即可。
总结:4 种结构的适用场景(一句话记牢)
| 结构 | 核心作用 | 适用场景 |
|---|---|---|
| raise | 主动抛出异常 | 业务逻辑校验(比如参数不合法) |
| assert | 调试时快速校验条件 | 开发阶段定位逻辑漏洞(不用于生产) |
| with 语句 | 自动管理资源 + 异常处理 | 文件、数据库、网络连接等资源操作 |
| 自定义异常类 | 处理业务专属异常 | 区分不同业务错误(比如余额不足、账号冻结) |
这些结构都和 try-except 配合使用(比如 raise 抛出的异常需要 try-except 捕获,with 内部隐含 try-except),核心都是让异常处理更精准、代码更简洁~
实际运用中的价值与意义
这些知识点绝对不是 “纸上谈兵”,而是你写 Python 代码时每天都会用到的核心技能—— 新手和高手的区别之一,就是是否会用异常处理让程序 “更靠谱、更好用”。
我用「零基础能理解的实际场景」,带你看清每个知识点的运用意义,结合你之前学的内容,一一对应上:
一、最直接的意义:程序不崩溃,别人敢用你的代码
新手写代码最常遇到的问题:“我自己用着好好的,别人一用就报错闪退”—— 核心原因就是没处理异常。
场景 1:你写了一个「个人计算器工具」(给家人 / 同学用)
- 没学异常处理时:同学输入
10 ÷ 0→ 程序直接崩溃,显示一堆看不懂的ZeroDivisionError,同学说 “你的工具不好用”;家人输入10 ÷ abc→ 程序又崩溃,显示ValueError,家人再也不用了。 - 学了
try-except后:用except ZeroDivisionError提示「除数不能为 0 哦」,用except ValueError提示「请输入数字」—— 程序不会崩,用户知道哪里错了,还能继续用。
场景 2:你写了一个「读取作业文件的程序」(课程作业)
- 没学异常处理时:作业文件被误删 / 移动了 → 程序崩溃,显示
FileNotFoundError,你的作业直接得 “运行失败”; - 学了
try-except后:捕获错误,提示「找不到作业文件,请检查文件是否在当前文件夹」,程序还能继续执行其他逻辑(比如让用户重新选择文件),作业能正常评分。
结论:异常处理的核心意义之一,是让程序从 “一碰就碎” 变成 “抗造耐用”—— 这是你写的代码能给别人用、能落地的基础。
二、给用户 / 自己「有用的提示」,而不是看不懂的报错
Python 默认的报错(比如 IndexError: list index out of range),对不懂编程的人来说就是 “天书”,就算是你自己,过几天再看也可能忘了哪里错了。
场景:你写了一个「班级成绩排名程序」(输入学生姓名查排名)
- 没异常处理时:用户输入了一个不存在的姓名(比如 “张三” 不在名单里)→ 程序崩溃,报错
KeyError: '张三',你得回头翻代码,猜是 “键不存在”; - 学了
try-except后:捕获KeyError,提示「抱歉,“张三” 不在班级名单中,请检查姓名是否正确」—— 用户能直接修正,你也不用花时间猜报错原因。
延伸:方便自己 debug(排查错误)
新手怕报错,但有异常处理的报错是 “有用的提示”,没处理的报错是 “没用的崩溃”:
# 有异常处理的debug
try:
file = open("成绩.txt", "r")
data = file.read()
except FileNotFoundError as e:
# 不仅提示,还记录错误详情和时间
print(f"2025-12-09 错误:找不到文件,原因:{e}")
except UnicodeDecodeError as e:
print(f"2025-12-09 错误:文件编码不对,原因:{e}")
就算程序出问题,你也能通过提示快速定位:是 “文件丢了” 还是 “文件格式错了”,不用对着一堆杂乱的报错信息发呆。
三、处理「不可控情况」,让程序更 “智能”
很多时候,程序出错不是你写得差,而是外部环境不可控(比如没网、文件被改、用户输错)—— 异常处理能让程序应对这些 “意外”,而不是直接罢工。
场景 1:你写了一个「简单的天气查询工具」(调用网络接口)
- 没异常处理时:电脑没网 → 程序崩溃,显示
ConnectionError;接口暂时故障 → 程序崩溃,显示TimeoutError; - 学了
try-except后:捕获这些错误,提示「网络异常,请检查网络连接」或「接口暂时不可用,请稍后再试」—— 用户知道该怎么做,程序也不会直接关掉。
场景 2:你写了一个「列表数据统计工具」(比如统计班级分数)
- 没异常处理时:你想访问第 5 个学生的分数,但班级只有 3 个学生(索引 4 越界)→ 程序崩溃,显示
IndexError; - 学了
try-except后:捕获IndexError,提示「班级只有 3 个学生,没有第 5 个学生哦」—— 程序继续统计其他数据,不会因为一个小错误导致整个统计任务失败。
四、finally:避免 “资源浪费”,新手最容易忽略的实用场景
finally 的 “必执行” 特性,核心作用是「释放资源」—— 不管程序有没有出错,都要做的收尾工作,新手如果没这个意识,很容易导致电脑资源被占用。
场景:你写了一个「批量读取小说的程序」(打开 100 个小说文件)
- 没学
finally时:读到第 30 个文件时,文件格式错误(报错UnicodeDecodeError)→ 程序崩溃,前 30 个打开的文件没关闭;这些没关闭的文件会占用电脑内存,多了会导致电脑变卡,甚至无法再次打开这些文件; - 学了
finally后:不管有没有报错,finally里的file.close()都会执行,确保每个打开的文件都被关闭,不会浪费资源。
其他常用场景:
- 连接数据库后,用
finally关闭连接(不然数据库会一直占着连接数,别人连不上); - 用浏览器爬取网页后,用
finally关闭浏览器(不然浏览器进程会一直后台运行)。
五、进阶意义:求职 / 做项目的 “基础门槛”
如果以后想靠 Python 找工作(比如做开发、数据分析),或者做稍微复杂的项目(比如自己开发一个小 APP),异常处理是必考点、必用技能:
- 面试时,你写的代码如果没有异常处理,面试官会觉得你 “考虑不周全”—— 实际工作中,程序要面对成千上万的用户,任何一个小异常导致崩溃,都可能影响很多人;
- 做项目时,比如开发一个电商小工具(计算订单金额),如果用户输错地址、支付失败,没有异常处理会导致订单数据错乱,而有异常处理能优雅回滚(比如提示 “支付失败,请重新支付”),保证数据安全。
总结:这些知识点能帮你从「“能跑” 到 “好用”」
- 新手阶段:用
try-except让你的小工具(计算器、成绩统计、文件读取)不崩溃、给用户友好提示,不用再因为 “别人输错一个值” 就重写代码; - 进阶阶段:用
try-except-else-finally处理复杂场景(网络请求、数据库操作、文件处理),保证程序稳定性和资源不浪费; - 求职 / 项目阶段:这是基础技能,能体现你的代码素养 ——“不仅能写出功能,还能考虑到各种意外情况”。
简单说:不会异常处理,你写的代码只是 “自己能玩的玩具”;会了异常处理,你写的代码才是 “别人能用的工具” —— 这就是最实际的意义~
如何在实际项目中运用 Python 异常处理?
在实际项目中,异常处理要围绕 “用户体验 + 程序稳定 + 问题排查”,针对项目里的高频风险场景(如用户交互、文件 / 网络 / 数据库操作)精准落地,以下是 4 个核心场景的实战用法:
场景 1:用户输入交互(项目里的前端 / 终端输入)
场景:让用户输入手机号、年龄等信息(易出现格式错误)处理思路:用try-except捕获值 / 类型错误,给用户友好提示
try:
age = int(input("请输入年龄:")) # 可能触发ValueError
except ValueError:
print("年龄必须是整数哦~请重新输入")
else:
print(f"您的年龄是{age}岁")
场景 2:文件操作(项目里的配置 / 数据读写)
场景:读取配置文件、导出数据(易出现文件不存在、权限不足)处理思路:用try-except-finally捕获文件相关异常,必关文件
import logging # 项目里要记录错误日志
file = None
try:
file = open("config.ini", "r")
config = file.read()
except FileNotFoundError:
print("配置文件丢失,请检查路径")
logging.error("config.ini文件不存在") # 记录错误便于排查
except PermissionError:
print("没有读取配置文件的权限")
logging.error("无config.ini读取权限")
finally:
if file:
file.close() # 必关文件,避免资源占用
场景 3:网络请求(项目里的 API 调用 / 爬虫)
场景:调用第三方接口(易出现网络超时、接口故障)处理思路:捕获网络类异常,给用户重试提示
import requests
try:
res = requests.get("https://api.xxx.com/data", timeout=5)
res.raise_for_status() # 捕获HTTP错误(如404/500)
except requests.exceptions.ConnectionError:
print("网络连接失败,请检查网络")
except requests.exceptions.Timeout:
print("接口响应超时,请稍后重试")
else:
print("接口数据获取成功:", res.json())
场景 4:数据库操作(项目里的数据存储)
场景:连接 MySQL 查询数据(易出现连接失败、SQL 语法错误)处理思路:捕获数据库相关异常,必关连接
import pymysql
conn = None
try:
conn = pymysql.connect(host="localhost", user="root", password="xxx")
cursor = conn.cursor()
cursor.execute("SELECT * FROM user")
except pymysql.OperationalError:
print("数据库连接失败,请检查账号密码")
except pymysql.ProgrammingError:
print("SQL语句写错啦~")
finally:
if conn:
conn.close() # 必关连接,避免数据库资源耗尽
项目中异常处理的通用原则
- 精准捕获:优先用具体异常(如
FileNotFoundError),少用Exception(避免掩盖未知错误); - 双端反馈:给用户友好提示,同时用
logging记录错误详情(方便开发者排查); - 资源必释:文件、数据库、网络连接等资源,用
finally或with语句(简化写法)确保关闭。
今日 Python 异常处理知识点梳理整合(总结笔记版)
核心逻辑:给程序装 “安全气囊”—— 出错不崩溃、处理更优雅,全程用 “生活比喻 + 简单逻辑” 串联,方便记忆!
一、基础认知:先搞懂 2 个核心概念
1. 什么是 “异常”?
- 通俗说:程序运行时的 “意外状况”(比如用户输错值、文件找不到、用 0 做除数);
- 后果:不处理就会让程序 “崩溃闪退”,给一堆看不懂的报错;
- 生活类比:开车时遇到 “爆胎、没油”—— 不算正常行驶,但提前准备就能避免失控。
2. 什么是 “报错”?
- 通俗说:异常发生时,Python 给出的 “问题提示”(告诉你 “哪里错、为什么错”);
- 解读技巧:所有报错都遵循
错误类型: 错误原因(标红行就是出错位置); - 常见报错(记 “场景 + 关键词”,看到就懂):
| 报错类型 | 通俗场景 | 核心关键词(一眼识别) |
|---|---|---|
| SyntaxError | 代码写错(漏括号、少冒号) | invalid syntax(语法无效) |
| ZeroDivisionError | 用 0 做除数 | division by zero(零除) |
| TypeError | 类型不匹配(数字 + 字符串) | unsupported operand type(不支持操作类型) |
| IndexError | 列表 / 字符串索引超界 | list index out of range(索引越界) |
| KeyError | 字典没有对应键 | KeyError: 'xxx'(键不存在) |
| ValueError | 值不合法(比如 int ("abc")) | invalid literal(无效值) |
二、核心工具 1:try-except(基础 “安全气囊”)
作用:监控可能出错的代码,出错后执行 “备用方案”
1. 语法结构(固定格式,照抄就行)
try:
# 可能出错的代码(只放核心监控部分)
代码A
except 错误类型1:
# 错误类型1发生时,执行的备用方案
代码B
except 错误类型2:
# 错误类型2发生时,执行的备用方案
代码C
2. 执行流程(3 步走,像 “安检” 一样)
- 先执行
try里的代码(监控区); - 如果没出错:跳过所有
except,程序继续往下跑; - 如果出错:找 “匹配的错误类型”,执行对应的备用方案,程序不崩溃。
3. 关键要点(记 2 个原则)
- 只把 “可能出错的代码” 放进
try(别把所有代码都塞进去,没必要); - 多个
except按 “从具体到通用” 的顺序写(比如先写 ZeroDivisionError,再写 Exception); - 应急技巧:不知道会出什么错时,用
except Exception as e(捕获所有常见错误,e 是错误详情)。
三、核心工具 2:try-except-else-finally(完整 “安全套装”)
作用:在 “基础安全气囊” 上,增加 “正常流程” 和 “必做收尾”,分工明确
1. 语法结构(4 个部分分工合作)
try:
# 可能出错的代码(监控区)
代码1
except 错误类型:
# 出错时执行(备用方案/救场)
代码2
else:
# 没出错时执行(正常流程的后续操作)
代码3
finally:
# 无论是否出错,必执行(收尾工作)
代码4
2. 各部分 “分工口诀”(一句话记牢)
- try:“监控员”—— 盯着可能出错的代码;
- except:“消防员”—— 出错了才上场,处理问题;
- else:“正常工”—— 没出错才干活,做后续操作;
- finally:“清洁工”—— 不管出不出错,必做收尾(关文件、关连接)。
3. 3 种执行场景(核心记忆点)
| 场景 | 执行顺序 | 程序状态 |
|---|---|---|
| try 块没出错 | try → else → finally | 正常运行 |
| try 块出错且匹配 except | try → except → finally | 正常运行 |
| try 块出错但不匹配 except | try → finally → 崩溃报错 | 崩溃(无对应救场) |
4. 生活类比(快速理解)
比如 “去餐厅吃饭”:
- try:点餐 + 吃饭(可能出错:没这个菜、没带钱);
- except:没这个菜就换菜,没带钱就刷手机(出错救场);
- else:吃得满意,加个甜品(没出错的后续);
- finally:不管吃没吃好,都要结账(必做收尾)。
四、实际运用:学了能解决什么问题?(3 个核心好处)
- 程序不崩溃:别人用你的工具(比如计算器、文件读取程序),输错值也不会闪退;
- 提示更友好:把 “ZeroDivisionError” 改成 “除数不能为 0 哦”,不懂编程的人也能看懂;
- 资源不浪费:用 finally 关闭文件、数据库连接,避免电脑资源被占用。
五、总结口诀(5 秒回忆所有核心)
try 监控,except 救场,else 补刀(没出错才上),finally 收场(必做收尾);报错看 “类型 + 原因”,异常处理让程序 “抗造又好用”!
作业:
理解今日的内容即可,可以检查自己过去借助 ai 写的代码是否带有 try-except机制,以后可以尝试采用这类写法增加代码健壮性。
实战项目:个人账单记录工具(异常处理专项练习)
一、项目说明
1. 项目目标
制作一个「本地账单记录工具」,支持添加账单、查询账单、保存账单到文件,全程用 try-except 系列机制处理所有可能的异常,同时练习 VS Code 的 debug 功能,吃透报错处理流程。
2. 适用环境
- 系统:Mac OS
- 工具:VS Code(已安装 Python 插件)
- 核心知识点:
try-except、try-except-else-finally、报错识别、debug 调试
3. 项目亮点
- 100% 覆盖当天所学知识点,所有易错点(用户输入错误、文件不存在、权限不足等)都用异常处理解决
- 提供完整操作流程(从 VS Code 配置到代码运行、debug)
- 分阶段实现,难度逐步提升,零基础友好
二、前置准备(Mac OS + VS Code 环境检查)
1. 确认 Python 已安装
打开 Mac 终端(Terminal),输入以下命令,若显示版本号则说明已安装(Python 3.7+ 均可):

2. VS Code 配置
- 打开 VS Code,安装 Python 插件:左侧扩展栏搜索「Python」(微软官方插件,蓝色图标),点击安装。
- 新建项目文件夹:在 Mac 桌面新建文件夹
bill_tool,打开 VS Code → 点击「文件」→「打开文件夹」→ 选择bill_tool。 - 新建 Python 文件:右键
bill_tool文件夹 →「新建文件」→ 命名为bill_manager.py(后缀必须是.py)。
3. 测试环境是否正常
在 bill_manager.py 中输入以下代码,点击右上角「运行」按钮(▶️),若输出 环境正常! 则配置成功:

三、项目需求拆解(要实现的功能)
- 功能 1:添加账单(输入账单类型、金额、日期,验证数据合法性)
- 功能 2:查询账单(按日期查询或查询所有账单)
- 功能 3:保存账单到文件(自动创建
bills.txt,支持重复写入) - 异常处理:覆盖以下 6 种常见错误(对应当天学的报错类型):
- ValueError:输入金额非数字、日期格式错误
- FileNotFoundError:读取未创建的账单文件
- PermissionError:文件无读写权限
- IndexError:查询时输入的索引越界
- TypeError:数据类型不匹配
- 通用异常:其他未预判的错误
四、分步实现(含完整代码 + 关键注释)
阶段 1:基础框架搭建(主菜单 + 全局变量)
先写程序入口和主菜单,用循环让工具持续运行,代码如下(直接复制到 bill_manager.py):
# 全局变量:存储账单列表(每个账单是字典)
bills = []
def main():
print("=" * 30)
print(" 个人账单记录工具(异常处理版)")
print("1. 添加账单 2. 查询账单 3. 保存账单 4. 退出")
print("=" * 30)
while True:
# 捕获用户输入选择时的异常
try:
choice = int(input("请输入功能编号(1-4):"))
except ValueError:
print("❌ 输入错误!请输入 1-4 之间的整数~")
continue # 重新循环,让用户再次输入
# 根据选择执行对应功能
if choice == 1:
add_bill()
elif choice == 2:
query_bill()
elif choice == 3:
save_bill()
elif choice == 4:
print("👋 感谢使用,再见!")
break
else:
print("❌ 功能编号不存在!请输入 1-4~")
if __name__ == "__main__":
main()
阶段 2:实现「添加账单」功能(重点:处理用户输入异常)
添加 add_bill() 函数,处理「金额非数字、日期格式错误」等异常,代码如下(添加到全局变量下方、main 函数上方):
def add_bill():
print("\n----- 添加账单 -----")
try:
# 1. 输入账单类型(字符串,无需强转,但可简单校验)
bill_type = input("请输入账单类型(如:餐饮、交通、购物):").strip()
if not bill_type:
raise ValueError("账单类型不能为空!") # 主动抛出异常
# 2. 输入金额(必须是数字,处理 ValueError)
amount = float(input("请输入账单金额(如:35.5):"))
if amount <= 0:
raise ValueError("金额必须大于 0!") # 主动抛出业务异常
# 3. 输入日期(格式:YYYY-MM-DD,简单校验长度)
date = input("请输入账单日期(格式:YYYY-MM-DD,如:2025-12-09):").strip()
if len(date) != 10 or date[4] != "-" or date[7] != "-":
raise ValueError("日期格式错误!请按 YYYY-MM-DD 输入~")
except ValueError as e:
print(f"❌ 添加失败:{e}")
else:
# 没出错时,将账单添加到列表
bill = {"类型": bill_type, "金额": round(amount, 2), "日期": date}
bills.append(bill)
print(f"✅ 账单添加成功!当前共 {len(bills)} 条账单")
finally:
print("----- 添加功能结束 -----\n")
阶段 3:实现「查询账单」功能(重点:处理索引越界异常)
添加 query_bill() 函数,支持「查询所有」和「按索引查询」,处理「索引越界、输入非数字」异常:
def query_bill():
print("\n----- 查询账单 -----")
if not bills:
print("⚠️ 暂无账单记录,请先添加账单~")
print("----- 查询功能结束 -----\n")
return
# 选择查询方式
try:
query_choice = int(input("请选择查询方式(1. 查询所有 2. 按索引查询):"))
if query_choice == 1:
# 查询所有账单
print("所有账单记录:")
for i, bill in enumerate(bills):
print(f"索引 {i}:日期 {bill['日期']} | 类型 {bill['类型']} | 金额 {bill['金额']} 元")
elif query_choice == 2:
# 按索引查询(处理 IndexError)
index = int(input(f"请输入要查询的索引(0-{len(bills)-1}):"))
bill = bills[index]
print(f"查询结果:")
print(f"日期:{bill['日期']} | 类型:{bill['类型']} | 金额:{bill['金额']} 元")
else:
raise ValueError("查询方式错误!只能输入 1 或 2~")
except ValueError as e:
print(f"❌ 查询失败:{e}")
except IndexError:
print(f"❌ 查询失败:索引越界!当前有效索引是 0-{len(bills)-1}")
finally:
print("----- 查询功能结束 -----\n")
阶段 4:实现「保存账单」功能(重点:文件操作异常 + try-except-else-finally)
添加 save_bill() 函数,处理「文件不存在、权限不足、编码错误」等异常,用 finally 确保资源释放:
def save_bill():
print("\n----- 保存账单 -----")
if not bills:
print("⚠️ 暂无账单记录,无需保存~")
print("----- 保存功能结束 -----\n")
return
# 文件保存路径(Mac 桌面,可自定义)
file_path = "/Users/你的用户名/Desktop/bills.txt" # 注意:替换成你的 Mac 用户名!
file = None # 初始化文件对象
try:
# 打开文件(a+:追加模式,不存在则创建;指定 UTF-8 编码避免中文乱码)
file = open(file_path, "a+", encoding="utf-8")
# 写入账单(每条账单占一行)
for bill in bills:
line = f"{bill['日期']},{bill['类型']},{bill['金额']}\n"
file.write(line)
# 清空账单列表(避免重复保存)
bills.clear()
except FileNotFoundError:
print("❌ 保存失败:文件路径不存在!请检查路径是否正确~")
except PermissionError:
print("❌ 保存失败:没有文件写入权限!请修改文件夹权限~")
except UnicodeEncodeError:
print("❌ 保存失败:账单包含非法字符,无法编码~")
except Exception as e:
# 捕获其他未知异常
print(f"❌ 保存失败:未知错误 - {e}")
else:
print(f"✅ 账单已成功保存到:{file_path}")
finally:
# 必关闭文件,避免资源占用
if file:
file.close()
print("📁 文件已安全关闭")
print("----- 保存功能结束 -----\n")
关键修改:替换文件保存路径
Mac 桌面路径格式是 /Users/你的用户名/Desktop/,比如你的用户名是 xiaoming,则路径改为:

五、完整操作流程(Mac OS + VS Code)
1. 运行程序
- 打开
bill_manager.py文件,确保所有代码复制完整,且替换了file_path中的用户名。 - 点击 VS Code 右上角的「运行」按钮(▶️),或按下快捷键
Ctrl + Alt + N(若安装了 Code Runner 插件)。 - 终端会显示主菜单,按提示输入数字操作:
- 先尝试输入非法值(比如选功能时输入
abc、金额输入-10、日期输入2025/12/09),观察异常处理效果。 - 再输入合法值添加 2-3 条账单,查询后保存到桌面。
2. Debug 调试练习(重点!熟悉报错处理流程)
VS Code 的 debug 功能能让你一步步看到代码执行过程,以及异常触发时的细节,操作步骤如下:
步骤 1:设置断点
在代码左侧行号旁点击,出现红色圆点(断点),推荐设置在这些位置:
add_bill()函数中try:那一行(行号大概 20 行左右)save_bill()函数中file = open(...)那一行(行号大概 80 行左右)
步骤 2:启动调试
- 点击 VS Code 左侧「运行和调试」图标(虫子图标),或按下
Cmd + Shift + D。 - 点击「创建 launch.json 文件」,选择「Python」→「当前文件」,自动生成调试配置。
- 点击调试面板左上角的「运行和调试」按钮(绿色三角),启动调试。
步骤 3:调试操作(核心快捷键)
- 「单步跳过」(快捷键
F10):执行下一行代码,不进入函数内部。 - 「单步调试」(快捷键
F11):进入函数内部,逐行执行。 - 「继续」(快捷键
F5):运行到下一个断点。 - 「停止」(快捷键
Shift + F5):结束调试。
调试练习:故意触发异常
- 启动调试后,选择功能 1(添加账单),输入金额
-5(触发主动抛出的ValueError)。 - 观察调试器:当执行到
raise ValueError("金额必须大于 0!")时,会跳转到except ValueError as e:行,在调试面板的「变量」中能看到e的值(错误信息)。 - 再尝试保存账单时,故意把
file_path改成错误路径(比如/Users/xxx/Desktop/bills.txt,xxx 是不存在的用户名),触发FileNotFoundError,观察调试流程。
3. 验证保存结果
- 成功保存账单后,打开 Mac 桌面,会看到
bills.txt文件。 - 双击打开文件,能看到保存的账单内容(逗号分隔),比如:

六、必须完成的练习任务(巩固知识点)
任务 1:测试所有异常场景(至少 5 种)
| 异常场景 | 操作步骤 | 预期结果(异常处理生效) |
|---|---|---|
| 功能选择输入非数字 | 主菜单输入 abc |
提示「输入错误!请输入 1-4 之间的整数~」 |
| 金额输入非数字 | 添加账单时金额输入 三十 |
提示「添加失败:could not convert string to float: ' 三十 '」 |
| 金额输入负数 | 添加账单时金额输入 -20 |
提示「添加失败:金额必须大于 0!」 |
| 日期格式错误 | 添加账单时日期输入 2025/12/09 |
提示「添加失败:日期格式错误!请按 YYYY-MM-DD 输入~」 |
| 按索引查询越界 | 只有 2 条账单时,查询索引输入 3 |
提示「查询失败:索引越界!当前有效索引是 0-1」 |
| 保存路径错误 | 故意写错 file_path 中的用户名 |
提示「保存失败:文件路径不存在!」 |
任务 2:修改代码,添加新的异常处理
在 add_bill() 函数中,添加「账单类型长度不能超过 10 字」的校验,主动抛出异常并处理:
# 在 bill_type = input(...) 之后添加:
if len(bill_type) > 10:
raise ValueError("账单类型不能超过 10 个字符!")
添加后,测试输入超过 10 字的类型(比如「超级无敌豪华大餐」),观察是否能正确捕获异常。
任务 3:用 try-except-else-finally 优化查询功能
在 query_bill() 的「查询所有」逻辑中,添加统计总金额的功能,用 else 执行统计,finally 输出总金额:
# 修改 query_choice == 1 的逻辑:
if query_choice == 1:
# 查询所有账单
total = 0.0 # 总金额
print("所有账单记录:")
try:
for i, bill in enumerate(bills):
print(f"索引 {i}:日期 {bill['日期']} | 类型 {bill['类型']} | 金额 {bill['金额']} 元")
total += bill['金额']
except TypeError:
print("❌ 统计失败:账单数据类型异常~")
else:
print(f"📊 所有账单总金额:{total:.2f} 元")
七、常见问题解决(Mac OS 专属)
1. 运行代码时提示「Python 未找到」
- 打开 VS Code → 左下角点击「Python x.x.x」(状态栏)→ 选择正确的 Python 路径(通常是
/usr/bin/python3或/Users/用户名/Library/Python/3.x/bin/python3)。
2. 保存文件时提示「PermissionError」(权限不足)
- 打开终端,输入以下命令(替换成你的文件路径),赋予读写权限:

3. 中文显示乱码
- 确保
open()函数中指定了encoding="utf-8",如代码中所示:file = open(file_path, "a+", encoding="utf-8")。
八、总结
这个项目完美覆盖了当天所学的核心知识点:
- 异常处理机制:通过「预判可能出错的场景」→「用 try 监控」→「except 处理」,让程序不崩溃;
- debug 报错:通过 VS Code 调试,直观看到异常触发、捕获、处理的完整流程;
- try-except 机制:每个功能都用了针对性的异常捕获(如 ValueError、IndexError);
- try-except-else-finally:保存功能中用
else提示成功,finally关闭文件。
完成所有练习后,你会发现:异常处理不是 “多余的代码”,而是让程序 “更靠谱” 的关键 —— 哪怕用户输入错误、文件路径不对,程序也能友好提示,而不是直接崩溃。
更多推荐


所有评论(0)