Python 异常处理完全指南(系统梳理+实战示例)
异常是 Python 程序运行时发生的意外错误(如除零、索引越界、文件不存在等),会导致程序默认终止并抛出错误信息。掌握异常处理,能让程序更健壮(避免崩溃)、错误排查更高效。本文从基础概念→核心语法→进阶用法→最佳实践,系统梳理 Python 异常知识。
一、异常的基本概念
1.1 异常 vs 语法错误
很多初学者会混淆「异常」和「语法错误」,两者本质不同:
| 类型 | 发生时机 | 能否捕获 | 示例 |
|---|---|---|---|
| 语法错误 | 程序执行前(解释器编译) | 不能 | 缩进错误、括号不匹配、语法错误 |
| 运行时异常 | 程序执行中 | 能 | 除零、索引越界、文件不存在 |
示例:语法错误(无法捕获)
print("Hello" # 缺少右括号,解释器直接报错,程序无法启动
报错信息:SyntaxError: unexpected EOF while parsing
示例:运行时异常(可捕获)
1 / 0 # 执行时触发除零异常,程序默认终止
报错信息:ZeroDivisionError: division by zero
1.2 异常的默认行为
当程序触发未处理的异常时,Python 会执行 3 步:
- 中断当前程序执行流程;
- 打印异常信息(包含异常类型、错误描述、出错位置的调用栈);
- 终止程序。
调用栈信息是调试关键,例如:
def divide(a, b):
return a / b
divide(1, 0)
报错信息包含调用栈(明确出错行):
ZeroDivisionError: division by zero
File "test.py", line 4, in <module>
divide(1, 0)
File "test.py", line 2, in divide
return a / b
二、异常处理的核心语法
Python 用 try-except-else-finally 语句捕获和处理异常,各子句分工明确,可灵活组合。
2.1 基础结构:try + except(核心)
作用:捕获 try 块中可能发生的异常,避免程序崩溃。
语法:
try:
# 可能触发异常的代码(核心执行逻辑)
危险操作
except 异常类型1:
# 当 try 块触发「异常类型1」时,执行此处代码
异常处理逻辑1
except 异常类型2:
# 当 try 块触发「异常类型2」时,执行此处代码
异常处理逻辑2
示例:捕获除零异常
try:
result = 1 / 0
except ZeroDivisionError:
print("错误:除数不能为 0!") # 捕获到异常,执行处理逻辑
print("程序继续执行...") # 不会终止,正常输出
输出:
错误:除数不能为 0!
程序继续执行...
2.2 捕获多个异常(2种方式)
方式1:多个 except 子句(推荐,逻辑清晰)
针对不同异常类型,编写不同处理逻辑:
try:
lst = [1, 2, 3]
print(lst[10]) # 触发 IndexError
# result = 1 / 0 # 触发 ZeroDivisionError
except IndexError:
print("错误:索引超出列表范围!")
except ZeroDivisionError:
print("错误:除数不能为 0!")
方式2:元组形式捕获多个异常(同一种处理逻辑)
如果多个异常的处理方式相同,可合并为一个 except:
try:
# 可能触发 IndexError 或 KeyError 的代码
d = {"name": "Python"}
print(d["age"]) # KeyError
except (IndexError, KeyError) as e: # as e:捕获异常对象,可获取详细信息
print(f"错误类型:{type(e).__name__}")
print(f"错误描述:{e}") # 打印具体错误信息
输出:
错误类型:KeyError
错误描述:'age'
2.3 捕获所有异常(谨慎使用)
不推荐写法:except:(捕获所有异常,包括系统退出异常)
try:
1 / 0
except: # 捕获所有异常(包括 KeyboardInterrupt、SystemExit 等系统异常)
print("发生了未知错误!")
问题:可能掩盖严重错误(如用户按 Ctrl+C 退出的 KeyboardInterrupt),导致调试困难。
推荐写法:except Exception:(捕获所有用户级异常)
Exception 是所有非系统退出异常的基类,不会捕获 SystemExit、KeyboardInterrupt 等系统级异常:
try:
1 / 0
except Exception as e: # 安全捕获所有用户异常
print(f"异常信息:{e}")
2.4 else 子句(可选)
作用:仅当 try 块没有触发任何异常时,才执行 else 中的代码(逻辑上是「无异常时的后续操作」)。
语法位置:在所有 except 之后,finally 之前。
try:
result = 10 / 2 # 无异常
except ZeroDivisionError:
print("除数不能为 0!")
else:
print(f"计算成功,结果:{result}") # 会执行
输出:计算成功,结果:5.0
2.5 finally 子句(可选)
作用:无论 try 块是否触发异常,finally 中的代码一定会执行(常用于「资源清理」,如关闭文件、数据库连接)。
语法位置:在所有 except/else 之后。
示例:文件操作的资源清理
file = None
try:
file = open("test.txt", "r") # 尝试打开文件
content = file.read()
except FileNotFoundError:
print("错误:文件不存在!")
else:
print("文件内容:", content)
finally:
# 无论是否异常,都关闭文件(避免资源泄露)
if file:
file.close()
print("文件已关闭")
注意:
finally中的return会覆盖try/except/else中的return(不推荐在finally中写return)。
2.6 上下文管理器(with 语句):简化资源清理
with 语句是 try-finally 的语法糖,自动帮你完成资源清理(如关闭文件、网络连接),无需手动写 finally:
# 等价于上面的文件操作示例,自动关闭文件
try:
with open("test.txt", "r") as file: # 进入 with 块:打开文件;退出 with 块:自动关闭
content = file.read()
except FileNotFoundError:
print("文件不存在!")
else:
print("文件内容:", content)
with 语句适用于所有实现了「上下文管理协议」的对象(如文件、数据库连接、锁等)。
三、Python 常见内置异常
Python 提供了大量内置异常(均继承自 BaseException),以下是开发中最常用的类型,按场景分类:
3.1 数据操作相关
| 异常类型 | 触发场景 | 示例 |
|---|---|---|
TypeError | 类型不匹配(如字符串+数字) | "2" + 3 |
ValueError | 值合法但不符合要求(如字符串转数字失败) | int("abc") |
ZeroDivisionError | 除零 | 1 / 0 |
OverflowError | 数值溢出(Python 中少见,如大整数运算) | pow(10, 1000000)(一般不触发) |
3.2 容器(列表/字典/元组)相关
| 异常类型 | 触发场景 | 示例 |
|---|---|---|
IndexError | 列表/元组索引越界 | [1,2,3][10] |
KeyError | 字典访问不存在的键 | {"a":1}["b"] |
AttributeError | 访问对象不存在的属性/方法 | s = "hello"; s.foo() |
IndexError | 字符串索引越界 | "abc"[5] |
3.3 文件/IO 相关
| 异常类型 | 触发场景 | 示例 |
|---|---|---|
FileNotFoundError | 打开不存在的文件 | open("不存在.txt") |
PermissionError | 无文件操作权限(如只读文件写入) | open("read_only.txt", "w") |
IOError | 通用 IO 错误(Python3 中部分合并到其他异常) | 磁盘满时写入文件 |
3.4 其他常见异常
| 异常类型 | 触发场景 | 示例 |
|---|---|---|
NameError | 访问未定义的变量/函数 | print(undefined_var) |
ImportError | 导入模块失败(模块不存在或路径错误) | import non_existent_module |
KeyboardInterrupt | 用户按 Ctrl+C 终止程序 | 运行时按 Ctrl+C |
MemoryError | 内存不足(如创建超大列表) | [1]*10**18 |
四、主动抛出异常:raise 语句
除了系统自动触发异常,你也可以用 raise 主动抛出异常(常用于「参数校验」「业务规则违规」等场景)。
4.1 基本用法
raise 异常类型 # 抛出指定类型的异常
raise 异常类型("错误描述") # 带自定义错误信息(推荐)
示例:参数校验
def register(name, age):
if not name:
# 主动抛出 ValueError,描述错误原因
raise ValueError("用户名不能为空!")
if age < 18:
raise ValueError(f"年龄需≥18,当前年龄:{age}")
print(f"注册成功:{name}({age}岁)")
try:
register("张三", 16) # 触发主动抛出的 ValueError
except ValueError as e:
print(f"注册失败:{e}") # 输出:注册失败:年龄需≥18,当前年龄:16
4.2 重新抛出异常(保留原始调用栈)
有时需要先捕获异常做一些处理,再重新抛出(不掩盖原始错误):
try:
1 / 0
except ZeroDivisionError as e:
print("记录错误日志:", e) # 先记录日志
raise # 重新抛出原始异常(保留调用栈)
# raise e # 等价于 raise,但 Python3 中推荐直接用 raise
4.3 异常链(raise ... from ...)
当一个异常导致另一个异常时,用 raise 新异常 from 原始异常 保留异常链(方便调试):
try:
open("data.txt") # 触发 FileNotFoundError
except FileNotFoundError as original_e:
# 抛出新异常,并关联原始异常
raise RuntimeError("数据文件缺失,无法继续执行") from original_e
报错信息会显示两个异常的关联关系:
RuntimeError: 数据文件缺失,无法继续执行
The above exception was the direct cause of the following exception:
FileNotFoundError: [Errno 2] No such file or directory: 'data.txt'
五、自定义异常
当 Python 内置异常无法满足业务需求时(如「用户不存在」「余额不足」等业务错误),可以自定义异常类。
5.1 自定义异常的规则
- 必须继承自
Exception(或其子类),不要继承BaseException(BaseException包含系统级异常,如SystemExit); - 通常只需要定义类名(语义化),无需额外方法(如需扩展可添加自定义属性)。
5.2 示例:业务自定义异常
# 自定义异常(继承 Exception)
class UserNotFoundError(Exception):
"""用户不存在异常(业务异常)"""
pass
class InsufficientBalanceError(Exception):
"""余额不足异常(业务异常)"""
def __init__(self, balance, need):
# 自定义异常属性,方便处理
self.balance = balance
self.need = need
# 调用父类构造器,设置错误描述
super().__init__(f"余额不足:当前 {balance},需 {need}")
# 使用自定义异常
def withdraw(user, amount):
users = {"张三": 1000, "李四": 500}
if user not in users:
raise UserNotFoundError(f"用户「{user}」不存在")
balance = users[user]
if balance < amount:
raise InsufficientBalanceError(balance, amount)
print(f"提现成功:{user} 提取 {amount} 元,剩余 {balance - amount} 元")
try:
withdraw("王五", 800)
except UserNotFoundError as e:
print(f"业务错误:{e}")
except InsufficientBalanceError as e:
print(f"业务错误:{e},当前余额:{e.balance}")
六、异常处理的最佳实践
掌握语法后,更重要的是规范使用异常处理,避免常见坑:
6.1 捕获具体异常,而非泛型异常
坏示例:用 except: 或 except Exception: 捕获所有异常,掩盖真实错误:
try:
lst = [1,2,3]
print(lst[10])
except: # 不推荐,无法定位错误类型
print("出错了!")
好示例:捕获具体异常,针对性处理:
try:
lst = [1,2,3]
print(lst[10])
except IndexError:
print("错误:索引越界,请检查列表长度!")
6.2 不要过度捕获异常
仅捕获「可预期、可处理」的异常,不要为了「避免崩溃」而捕获所有异常。例如,KeyboardInterrupt(用户终止程序)不应捕获:
try:
while True:
print("运行中...")
except Exception: # 不会捕获 KeyboardInterrupt,用户可正常终止
print("处理已知异常...")
6.3 提供清晰的错误信息
异常信息应明确说明「什么错、为什么错、怎么改」,方便调试和用户理解:
# 坏示例
raise ValueError("参数错误")
# 好示例
raise ValueError("参数 age 必须是 18-60 之间的整数,当前值:'abc'")
6.4 用 finally 或 with 清理资源
文件、网络连接、数据库连接等资源,必须确保关闭,优先用 with 语句,其次用 finally:
# 推荐:with 自动关闭数据库连接(假设使用 sqlite3)
import sqlite3
with sqlite3.connect("test.db") as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")
# 备选:finally 手动关闭
conn = None
try:
conn = sqlite3.connect("test.db")
# 数据库操作
except Exception as e:
print("数据库错误:", e)
finally:
if conn:
conn.close()
6.5 避免沉默异常(Swallowed Exceptions)
捕获异常后,要么处理(修复错误、提示用户),要么重新抛出,不要捕获后不做任何操作:
# 坏示例:沉默异常,无法排查问题
try:
1 / 0
except ZeroDivisionError:
pass # 不做任何处理,错误被掩盖
# 好示例:要么处理,要么抛出
try:
1 / 0
except ZeroDivisionError as e:
print(f"错误:{e}") # 处理(提示用户)
# 或重新抛出(如果当前无法处理)
# raise
6.6 自定义异常用语义化命名
自定义异常类名应包含「错误场景」,以 Error 结尾(如 UserNotFoundError、PaymentFailedError),便于阅读和区分。
七、总结
Python 异常处理的核心是「预判错误→捕获错误→处理错误→清理资源」,关键知识点:
- 异常是运行时错误,区别于语法错误;
- 核心语法:
try-except-else-finally,with语句简化资源清理; - 捕获异常:优先具体异常,慎用泛型捕获;
- 主动抛出:
raise用于参数校验、业务违规; - 自定义异常:继承
Exception,语义化命名; - 最佳实践:不沉默异常、不过度捕获、清晰错误信息。
通过合理的异常处理,你的 Python 程序将更健壮、更易维护!
538

被折叠的 条评论
为什么被折叠?



