Python 零基础入门系列(十三):异常处理
🛡️ Python 零基础入门系列(十三):异常处理
让程序在“暴风雨”中优雅地运行,而不是轰然倒塌
📢 系列说明:
在前一篇中,我们学会了如何通过文件操作让数据“永生”。但在真实的开发环境中,程序往往会遇到各种意外情况:文件被删除了、网络断开了、用户输入了非法字符……
本文将教你如何使用**异常处理(Exception Handling)**机制,捕获并处理这些运行时错误,让你的程序更加健壮和稳定。📅 更新时间:2026 年 3 月 30 日
🎯 本篇你将学到:常见异常类型、
try-except语句、else和finally子句、主动抛出异常、自定义异常⏱️ 预计阅读时间:40-50 分钟 | 💻 实践时间:30-40 分钟
📌 前置知识:已完成前十二篇(特别是文件操作和逻辑判断)
✍️ 作者:书到用时方恨少!
🌟 前言:程序的“意外”时刻
在之前的练习中,如果你输入了错误的代码(比如用数字除以字符串),Python 解释器会直接报错并停止运行,终端里会打印出一长串红色的英文信息。
在编程术语中,这被称为异常(Exception)。如果不处理这些异常,程序就会非正常终止(Crash)。
异常处理的核心思想是:“防患于未然”。我们预判某些代码可能会出错,于是提前写好“应急预案”。当错误发生时,程序不会崩溃,而是执行我们准备好的处理逻辑(比如提示用户重新输入、记录日志、使用默认值等)。
1. 异常基础
1.1 什么是异常(Exception)
异常是 Python 中的一个对象,它代表程序运行过程中发生的错误。当 Python 无法正常处理代码时,就会创建一个异常对象。
1.2 常见的内置异常类型
Python 内置了许多异常类型,用来区分不同的错误场景。以下是开发中最常遇到的几种:
| 异常类型 | 说明 | 触发场景 |
|---|---|---|
SyntaxError |
语法错误 | 代码写错了(如缺少冒号、括号不匹配),无法被捕获,必须修改代码 |
IndentationError |
缩进错误 | 缩进不规范 |
NameError |
名称错误 | 使用了未定义的变量名 |
TypeError |
类型错误 | 操作或函数应用于不适当类型的对象(如 '2' + 2) |
ValueError |
值错误 | 对象类型正确,但值不合法(如 int('abc')) |
IndexError |
索引错误 | 序列下标越界 |
KeyError |
键错误 | 字典中找不到指定的键 |
FileNotFoundError |
文件不存在 | 试图打开不存在的文件 |
ZeroDivisionError |
除零错误 | 除数为零 |
1.3 异常的传播(Traceback)
当你没有处理异常时,它会沿着函数调用栈向上传播,直到被处理或导致程序终止。
示例:
def divide(a, b):
return a / b # 这里可能抛出 ZeroDivisionError
def main():
divide(10, 0) # 调用 divide
main() # 调用 main
如果发生除零错误,Python 会打印:
Traceback (most recent call last):
File "test.py", line 7, in <module>
main()
File "test.py", line 5, in main
divide(10, 0)
File "test.py", line 2, in divide
return a / b
ZeroDivisionError: division by zero
这就是堆栈跟踪(Traceback),它能帮你精准定位错误发生的位置。
2. 捕获异常
2.1 try-except 语句
这是异常处理的核心语法。基本结构是:先尝试执行 try 块中的代码,如果出错,就跳转到 except 块处理。
语法:
try:
# 可能出现错误的代码
risky_code()
except:
# 如果出错,执行这里的代码
handle_error()
2.2 捕获特定异常
永远不要裸奔 except:。如果不指定异常类型,它会捕获所有错误,包括你意想不到的致命错误,这会让调试变得非常困难。
正确写法:
try:
num = int(input("请输入一个数字: "))
except ValueError: # 只捕获类型转换错误
print("输入的不是有效数字,请重新输入!")
2.3 捕获多个异常
如果一段代码可能抛出多种不同的异常,你可以使用多个 except 块。
try:
file = open('data.txt', 'r')
content = file.read()
number = int(content)
result = 10 / number
except FileNotFoundError:
print("错误:找不到文件 data.txt")
except ValueError:
print("错误:文件内容不是有效的数字")
except ZeroDivisionError:
print("错误:不能除以零")
2.4 捕获异常对象(as)
有时候你不仅想知道出错了,还想获取具体的错误信息(比如错误原因)。可以使用 as 关键字。
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"程序出错了:{e}") # 输出:程序出错了:division by zero
2.5 万能捕获(慎用)
虽然可以使用 Exception 来捕获几乎所有的常规错误,但通常建议将其放在 except 块的最后,作为兜底方案。
try:
# 一些复杂的操作
pass
except FileNotFoundError as e:
print(f"文件错误: {e}")
except Exception as e: # 捕获其他所有未预料到的错误
print(f"未知错误: {e}")
# 通常在这里记录日志,方便后续排查
3. 完整的异常处理结构
一个完整的 try 语句块可以包含 else 和 finally 子句。
3.1 else 子句
else 块在 try 块没有发生任何异常时执行。它通常用于放置那些只有在没有错误时才应该运行的代码,以避免意外捕获这些代码产生的错误。
对比:
# 方式 A:没有 else
try:
num = int(input("输入数字: "))
print(f"你输入了: {num}")
# 如果 print 报错,也会被下面的 except 捕获(虽然概率极低)
except ValueError:
print("输入错误")
# 方式 B:推荐使用 else
try:
num = int(input("输入数字: "))
except ValueError:
print("输入错误")
else:
# 只有转换成功才会执行
print(f"你输入了: {num}")
# 这里的 print 报错会直接抛出,不会被上面的 except 捕获
3.2 finally 子句
finally 块无论是否发生异常,都会在最后执行。它通常用于释放关键资源,例如关闭文件、关闭数据库连接、释放锁等。
语法:
try:
file = open("test.txt", "w")
file.write("Hello")
# 假设这里发生了一个未被捕获的致命错误
raise RuntimeError("模拟崩溃")
except ValueError:
print("处理值错误")
finally:
# 即使上面 raise 了 RuntimeError,这里依然会执行!
file.close()
print("文件已关闭")
执行顺序总结:
- 执行
try块。 - 如果有异常,寻找匹配的
except块并执行。 - 如果没有异常,执行
else块。 - 无论前面发生了什么(即使程序要崩溃了),最后都必须执行
finally块。
4. 主动抛出异常
除了 Python 自动抛出的异常,我们也可以在代码中主动抛出异常。
4.1 raise 语句
当你检测到某种条件不满足,无法继续执行时,可以使用 raise 抛出异常。
示例:
def withdraw_money(balance, amount):
if amount > balance:
# 主动抛出异常,终止函数执行
raise ValueError("余额不足,无法取款!")
balance -= amount
return balance
# 调用
try:
withdraw_money(100, 200)
except ValueError as e:
print(e) # 输出:余额不足,无法取款!
4.2 重新抛出异常
在 except 块中处理完日志记录等逻辑后,如果你想让上层调用者也知道这个错误,可以使用 raise 不带参数重新抛出当前的异常。
try:
risky_operation()
except Exception as e:
print(f"记录日志:操作失败,原因:{e}")
# 记录完日志后,继续向上抛出错误,让上层处理
raise
5. 自定义异常
Python 内置的异常类型虽然很多,但有时无法精确表达业务逻辑中的错误。这时我们可以创建自定义异常。
方法:定义一个继承自 Exception 类(或其子类)的新类。
场景:开发一个在线购物系统,需要处理“库存不足”的业务异常。
# 自定义异常类
class InsufficientStockError(Exception):
"""库存不足异常"""
def __init__(self, product_name, stock, requested):
self.product_name = product_name
self.stock = stock
self.requested = requested
# 调用父类构造函数
super().__init__(f"商品 '{product_name}' 库存不足。库存: {stock}, 请求: {requested}")
# 模拟下单函数
def place_order(product, quantity):
stock = 10 # 假设库存只有 10 个
if quantity > stock:
# 抛出自定义异常
raise InsufficientStockError(product, stock, quantity)
print(f"订单成功:购买 {quantity} 个 {product}")
# 调用与处理
try:
place_order("iPhone", 15)
except InsufficientStockError as e:
print(f"业务错误:{e}")
print(f"请联系客服。商品:{e.product_name}")
6. 异常处理最佳实践
- 具体优于宽泛:
尽量捕获具体的异常类型(如ValueError),而不是裸奔except:。这能防止掩盖真正的 Bug。 - 不要忽略异常:
即使你写了except,也至少要打印日志或记录下来。空的except块会让程序在出错时静默失败,极难排查。 - 使用
finally释放资源:
文件、网络连接、锁等资源必须在finally中释放,或者使用with语句(上下文管理器)自动管理。 - 尽早抛出,晚点捕获:
在错误发生的源头尽早抛出异常,但在有足够信息处理它的地方才捕获。 - 利用异常传递信息:
通过自定义异常,可以将业务逻辑中的错误清晰地表达出来。
7. 综合实战:带异常处理的文件备份工具
我们将结合前几篇学的文件操作、os 模块和异常处理,编写一个健壮的文件备份脚本。
功能要求:
- 用户输入源文件路径和目标备份路径。
- 程序检查源文件是否存在。
- 程序尝试复制文件。
- 处理各种可能的错误(路径无效、权限不足、磁盘满等)。
import os
import shutil
def backup_file():
print("=== 欢迎使用安全文件备份工具 ===")
src = input("请输入要备份的文件路径: ").strip()
dst = input("请输入备份目标路径(含文件名): ").strip()
try:
# 1. 验证源文件
if not os.path.exists(src):
raise FileNotFoundError(f"源文件不存在:{src}")
if not os.path.isfile(src):
raise ValueError(f"路径存在,但不是一个文件:{src}")
# 2. 验证目标目录
dst_dir = os.path.dirname(dst)
if dst_dir and not os.path.exists(dst_dir):
# 询问是否创建目录
create = input(f"目标目录不存在 {dst_dir},是否自动创建?(y/N): ")
if create.lower() != 'y':
return # 用户取消
os.makedirs(dst_dir, exist_ok=True)
# 3. 执行备份
print(f"正在备份 {src} 到 {dst} ...")
shutil.copy2(src, dst) # copy2 会保留元数据
print("✅ 备份成功!")
except PermissionError as e:
print(f"❌ 权限错误:无法读取源文件或写入目标位置。")
print(f" 详情:{e}")
except FileNotFoundError as e:
print(f"❌ 文件未找到错误:{e}")
except OSError as e:
# 捕获磁盘空间不足等操作系统级错误
print(f"❌ 磁盘或系统错误:可能是磁盘已满或路径过长。")
print(f" 详情:{e}")
except Exception as e:
# 兜底异常
print(f"❌ 发生未知错误:{e}")
finally:
print("操作结束。")
if __name__ == "__main__":
backup_file()
🎉 结语
通过本篇的学习,你已经掌握了 Python 程序的“安全带”——异常处理。
- 你学会了使用
try-except-else-finally结构来捕获和处理错误。 - 你理解了如何通过
raise主动抛出异常。 - 你掌握了自定义异常来处理业务逻辑错误。
- 你学会了编写健壮的代码,防止程序因意外输入而崩溃。
现在,你的程序已经具备了应对“意外”的能力。
下一步建议:
回顾你之前写的代码(比如计算器或文件操作脚本),尝试加入异常处理机制,看看程序的稳定性提升了多少。
🚀 下一篇预告:《Python 零基础入门系列(十四):综合实战项目》
这是本系列的收官之作。我们将把前 13 篇学到的所有知识(语法、函数、模块、文件、异常等)融合在一起,从零开始开发一个完整的 “个人记账管理系统”。敬请期待!
📅 更新时间:2026 年 3 月 30 日
✍️ 作者:书到用时方恨少!
更多推荐


所有评论(0)