Python异常捕获全攻略:从基础语法到项目实战
Python内置异常无法覆盖所有业务场景(如“余额不足”“用户不存在”等),此时可以自定义异常类,继承自Exception(推荐)或。Exception基础结构try-except捕获异常,else执行正常逻辑,finally释放资源,raise主动抛异常;精准捕获:优先捕获指定异常,避免裸except,使用as关键字获取异常详情;进阶用法:自定义异常满足业务需求,异常链保留原始上下文,trace
在Python编程中,“异常”是不可避免的——文件不存在、网络中断、数据类型不匹配、索引越界等场景,都可能导致程序意外崩溃。如果不进行异常处理,不仅会影响用户体验,还可能导致数据丢失、资源泄露等严重问题。
异常处理的核心不是“隐藏错误”,而是“优雅地处理错误”,让程序在遇到异常时能够正常降级、保留上下文信息,同时方便开发者排查问题。本文将从**基础语法、进阶用法、实战场景、最佳实践**四个维度,全面讲解Python异常捕获的方式,帮你写出更健壮、更易维护的Python代码。
一、先搞懂:Python中的异常类型
在学习捕获异常之前,我们先明确Python中常见的异常类型,这是精准捕获异常的基础。
|
异常类型 |
说明 |
常见场景 |
|
|
语法错误 |
代码缩进错误、括号不匹配、关键字拼写错误(不属于运行时异常,无法通过 |
|
|
名称错误 |
引用未定义的变量、函数名拼写错误 |
|
|
类型错误 |
不同类型数据进行无效操作(如字符串+数字) |
|
|
值错误 |
数据类型正确但值无效(如 |
|
|
文件未找到错误 |
读取不存在的文件 |
|
|
索引越界错误 |
访问列表/元组不存在的索引 |
|
|
键错误 |
访问字典不存在的键 |
|
|
属性错误 |
访问对象不存在的属性/方法 |
|
|
IO错误 |
文件读写失败(如权限不足) |
|
|
网络请求错误 |
(第三方库)网络断开、请求超时、状态码异常 |
注意:
SyntaxError是语法错误,在程序运行前就会被解释器检测到,无法通过异常处理机制捕获;其他异常均为运行时异常,是我们处理的重点。
二、基础语法:Python异常捕获的核心结构
Python异常处理的核心是try-except语句,配合else、finally、raise,构成完整的异常处理体系。
1. 最基础:try-except(捕获指定异常)
这是异常处理的最小单元,用于捕获并处理指定类型的异常。
语法结构
try:
# 可能抛出异常的代码块(业务逻辑)
risky_code()
except 异常类型1:
# 捕获到异常类型1时执行的逻辑
handle_exception1()
except 异常类型2:
# 捕获到异常类型2时执行的逻辑
handle_exception2()
代码示例
# 示例1:捕获单个异常
def convert_to_int(s):
try:
return int(s)
except ValueError:
print(f"错误:'{s}' 无法转换为整数")
return None
# 测试
print(convert_to_int("123")) # 正常执行:123
print(convert_to_int("abc")) # 捕获异常:错误:'abc' 无法转换为整数 → None
# 示例2:捕获多个异常
def calculate(a, b, op):
try:
if op == "+":
return a + b
elif op == "/":
return a / b
else:
raise ValueError(f"不支持的操作:{op}")
except TypeError:
print("错误:a和b必须是数字类型")
return None
except ZeroDivisionError:
print("错误:除数不能为0")
return None
# 测试
print(calculate(10, 2, "+")) # 12
print(calculate(10, 0, "/")) # 错误:除数不能为0 → None
print(calculate(10, "2", "+")) # 错误:a和b必须是数字类型 → None
2. 捕获所有异常:except(裸except)
使用不带异常类型的except可以捕获所有运行时异常,适合快速调试或兜底处理。
语法示例
def risky_operation():
try:
# 复杂业务逻辑,可能抛出多种异常
lst = [1,2,3]
print(lst[10]) # 索引越界
print("abc" + 123) # 类型错误
except: # 捕获所有运行时异常
print("发生了未知异常,程序已降级处理")
return False
return True
# 测试
risky_operation() # 输出:发生了未知异常,程序已降级处理
警告:生产环境中尽量避免使用“裸except”!它会捕获所有异常(包括
KeyboardInterrupt(用户中断)、SystemExit(程序退出)等),可能隐藏严重问题,且难以排查根因。
3. 增强版:try-except-else
else子句用于执行“当try代码块无异常时”的逻辑,将“正常业务”与“异常处理”分离,代码更清晰。
语法示例
def read_file_content(file_path):
try:
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
except FileNotFoundError:
print(f"错误:文件 {file_path} 不存在")
return ""
else:
# 仅当try块无异常时执行
print(f"成功读取文件 {file_path},内容长度:{len(content)}")
return content
# 测试
read_file_content("test.txt") # 若文件存在,执行else;不存在则执行except
4. 必执行:try-except-finally
finally子句中的代码**无论****try****块是否抛出异常,都会执行**,常用于释放资源(如关闭文件、数据库连接、网络连接等)。
语法示例
# 示例1:手动关闭文件(不使用with语句时)
def read_file_manual(file_path):
f = None
try:
f = open(file_path, "r", encoding="utf-8")
content = f.read()
print(f"文件内容:{content}")
return content
except FileNotFoundError:
print(f"错误:文件 {file_path} 不存在")
return ""
finally:
# 无论是否异常,都关闭文件
if f is not None:
f.close()
print("文件已关闭")
# 测试
read_file_manual("test.txt")
# 示例2:模拟释放数据库连接
def connect_db():
print("建立数据库连接")
return "db_connection"
def close_db(conn):
print("关闭数据库连接")
def query_db(sql):
conn = None
try:
conn = connect_db()
print(f"执行SQL:{sql}")
# 模拟查询异常
if "error" in sql:
raise IOError("数据库查询失败")
return "查询结果"
except IOError as e:
print(f"错误:{e}")
return None
finally:
if conn is not None:
close_db(conn)
# 测试
query_db("SELECT * FROM user") # 正常执行:建立连接→执行SQL→关闭连接
query_db("SELECT * FROM error") # 异常执行:建立连接→抛出异常→关闭连接
提示:Python中的
with语句(上下文管理器)是try-finally的语法糖,可自动释放资源(如文件操作),优先使用with而非手动finally关闭资源。
5. 主动抛异常:raise语句
除了Python内置异常,我们还可以通过raise主动抛出异常(内置异常或自定义异常),用于业务逻辑的异常提示(如参数校验失败、业务规则违规等)。
语法示例
# 示例1:抛出内置异常
def check_age(age):
if not isinstance(age, int):
raise TypeError("年龄必须是整数类型")
if age < 0 or age > 150:
raise ValueError(f"年龄 {age} 无效,必须在0-150之间")
print(f"年龄 {age} 验证通过")
# 测试
try:
check_age(200) # 抛出ValueError
except (TypeError, ValueError) as e:
print(f"错误:{e}")
# 示例2:抛出异常并附带详细信息
def transfer_money(from_account, to_account, amount):
if amount <= 0:
raise ValueError("转账金额必须大于0", {"from": from_account, "to": to_account, "amount": amount})
# 模拟转账逻辑
print(f"成功从 {from_account} 转账 {amount} 到 {to_account}")
# 测试
try:
transfer_money("A123", "B456", -100)
except ValueError as e:
print(f"转账失败:{e.args[0]}")
print(f"异常详情:{e.args[1]}")
三、进阶用法:提升异常处理的灵活性
1. 捕获异常的详细信息:as关键字
使用except 异常类型 as e可以捕获异常实例,获取异常的详细信息(如错误描述、堆栈信息等),方便排查问题。
import traceback
def test_exception_detail():
try:
lst = [1,2,3]
print(lst[10])
except IndexError as e:
# 1. 获取错误描述
print(f"错误描述:{e}")
print(f"错误类型:{type(e).__name__}")
# 2. 获取完整堆栈信息(便于调试)
print("\n完整堆栈信息:")
traceback.print_exc() # 打印堆栈信息到控制台
# 3. 将堆栈信息写入日志文件
with open("error.log", "a", encoding="utf-8") as f:
traceback.print_exc(file=f)
# 测试
test_exception_detail()
2. 自定义异常:满足业务个性化需求
Python内置异常无法覆盖所有业务场景(如“余额不足”“用户不存在”等),此时可以自定义异常类,继承自Exception(推荐)或BaseException。
语法示例
# 1. 定义自定义异常(继承自Exception)
class BusinessException(Exception):
"""业务异常基类"""
def __init__(self, code, message):
self.code = code # 错误码
self.message = message # 错误信息
super().__init__(f"[{code}] {message}")
# 2. 定义具体业务异常
class InsufficientBalanceException(BusinessException):
"""余额不足异常"""
def __init__(self, account, balance, need_amount):
self.account = account
self.balance = balance
self.need_amount = need_amount
super().__init__(
code=1001,
message=f"账户 {account} 余额不足:当前 {balance},需要 {need_amount}"
)
class UserNotExistException(BusinessException):
"""用户不存在异常"""
def __init__(self, user_id):
self.user_id = user_id
super().__init__(
code=1002,
message=f"用户 {user_id} 不存在"
)
# 3. 使用自定义异常
def deduct_balance(user_id, amount):
# 模拟用户查询
user_map = {"U001": 1000, "U002": 500}
if user_id not in user_map:
raise UserNotExistException(user_id)
balance = user_map[user_id]
if balance < amount:
raise InsufficientBalanceException(user_id, balance, amount)
# 模拟扣款
user_map[user_id] -= amount
print(f"用户 {user_id} 扣款成功,剩余余额:{user_map[user_id]}")
# 4. 捕获自定义异常
try:
deduct_balance("U002", 1000)
except BusinessException as e:
print(f"业务异常:{e}")
print(f"错误码:{e.code}")
# 针对具体异常做特殊处理
if isinstance(e, InsufficientBalanceException):
print(f"补充提示:请为账户 {e.account} 充值")
3. 异常链:保留原始异常上下文
Python 3.3+ 支持raise 新异常 from 原始异常,用于构建异常链,保留原始异常的上下文信息,便于排查嵌套异常。
def parse_json(json_str):
import json
try:
data = json.loads(json_str)
except json.JSONDecodeError as e:
# 抛出业务异常,并保留原始异常上下文
raise BusinessException(2001, "JSON解析失败") from e
return data
# 测试
try:
parse_json("{name: '张三'}") # 无效JSON
except BusinessException as e:
print(f"错误:{e}")
# 打印原始异常
print(f"原始异常:{e.__cause__}")
四、实战场景:异常处理的实际应用
场景1:文件操作中的异常处理
文件操作是异常高发场景(文件不存在、权限不足、编码错误等),需针对性捕获异常。
def file_operation_demo(file_path):
try:
# 读取文件
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
# 写入新文件
with open(f"{file_path}.bak", "w", encoding="utf-8") as f:
f.write(content)
print(f"成功备份文件 {file_path} 到 {file_path}.bak")
except FileNotFoundError:
print(f"错误:文件 {file_path} 不存在")
except PermissionError:
print(f"错误:没有权限读写文件 {file_path}")
except UnicodeDecodeError:
print(f"错误:文件 {file_path} 编码不是utf-8")
except Exception as e:
print(f"未知错误:{e}")
else:
return True
return False
# 测试
file_operation_demo("test.txt")
场景2:网络请求中的异常处理
使用requests库进行网络请求时,需捕获网络异常、超时异常、状态码异常等。
import requests
from requests.exceptions import RequestException, ConnectTimeout, HTTPError
def http_request_demo(url, timeout=5):
try:
response = requests.get(url, timeout=timeout)
# 主动抛出状态码异常(4xx/5xx)
response.raise_for_status()
return response.json()
except ConnectTimeout:
print(f"错误:连接 {url} 超时({timeout}秒)")
except HTTPError as e:
print(f"错误:请求 {url} 失败,状态码:{e.response.status_code}")
except RequestException as e:
print(f"网络请求错误:{e}")
except ValueError:
print(f"错误:{url} 返回的不是有效JSON")
return None
# 测试
http_request_demo("https://api.github.com/users/github") # 正常请求
http_request_demo("https://api.github.com/invalid", timeout=2) # 404异常
场景3:数据处理中的异常处理
数据转换、索引访问、字典键访问等场景,需处理类型错误、值错误、索引错误等。
def data_process_demo(data_list):
result = []
for idx, item in enumerate(data_list):
try:
# 数据转换
id = int(item.get("id"))
name = str(item.get("name")).strip()
age = int(item.get("age", 0))
# 数据校验
if not name:
raise ValueError("姓名不能为空")
result.append({"id": id, "name": name, "age": age})
except KeyError as e:
print(f"第 {idx} 条数据错误:缺少字段 {e}")
except (TypeError, ValueError) as e:
print(f"第 {idx} 条数据错误:{e}")
except Exception as e:
print(f"第 {idx} 条数据未知错误:{e}")
return result
# 测试数据
test_data = [
{"id": "1", "name": "张三", "age": "20"},
{"id": "2", "name": "", "age": "25"}, # 姓名为空
{"id": "abc", "name": "李四"}, # id无法转整数
{"name": "王五", "age": "30"} # 缺少id字段
]
# 执行
print(data_process_demo(test_data))
场景4:函数参数校验的异常处理
使用异常处理进行函数参数校验,比手动if判断更简洁,且能统一异常处理逻辑。
def add(a, b):
"""加法函数,仅支持数字类型"""
try:
return float(a) + float(b)
except (TypeError, ValueError) as e:
raise TypeError(f"参数a和b必须是数字类型:{e}") from e
# 测试
try:
print(add(10, 20))
print(add(10, "abc"))
except TypeError as e:
print(f"错误:{e}")
五、最佳实践与避坑指南
1. 最佳实践
(1)优先捕获指定异常,避免裸except
# 不好的写法
try:
risky_code()
except:
pass # 隐藏所有异常,难以排查
# 好的写法
try:
risky_code()
except (FileNotFoundError, ValueError) as e:
print(f"处理已知异常:{e}")
except Exception as e:
print(f"记录未知异常:{e}")
raise # 重新抛出异常,不隐藏问题
(2)异常粒度适中,避免过度拆分
# 不好的写法(过度拆分,代码冗余)
try:
a = int(x)
except ValueError:
print("x无法转整数")
try:
b = int(y)
except ValueError:
print("y无法转整数")
# 好的写法(合理合并,代码简洁)
try:
a = int(x)
b = int(y)
except ValueError as e:
print(f"数据转换失败:{e}")
(3)使用with语句自动释放资源
# 不好的写法(手动关闭资源,容易遗漏)
f = open("test.txt", "r")
try:
content = f.read()
finally:
f.close()
# 好的写法(with自动关闭资源)
with open("test.txt", "r", encoding="utf-8") as f:
content = f.read()
(4)异常信息要明确,便于排查
# 不好的写法(信息模糊)
except ValueError:
raise ValueError("数据错误")
# 好的写法(信息明确,包含上下文)
except ValueError:
raise ValueError(f"用户ID {user_id} 转换失败,原始值:{raw_id}")
(5)自定义异常继承Exception而非BaseException
BaseException包含SystemExit、KeyboardInterrupt等系统异常,自定义异常继承Exception可避免捕获系统异常。
2. 避坑指南
(1)不要捕获异常后不处理(“吞掉”异常)
# 危险写法:捕获异常后无任何处理,隐藏问题
try:
db.insert(data)
except DatabaseError:
pass # 数据插入失败但无提示,导致业务数据不一致
(2)不要在finally中返回值
finally中的return会覆盖try/except中的返回值,导致异常信息丢失。
# 危险写法
def test_finally_return():
try:
1 / 0
return "正常返回"
except ZeroDivisionError:
return "异常返回"
finally:
return "finally返回" # 会覆盖上面的返回值
# 测试
print(test_finally_return()) # 输出:finally返回
(3)不要滥用异常处理替代正常判断
# 不好的写法(用异常处理替代正常判断)
def get_user_name(user_list, user_id):
try:
return next(u for u in user_list if u["id"] == user_id)["name"]
except StopIteration:
return None
# 好的写法(正常判断,更高效)
def get_user_name(user_list, user_id):
for u in user_list:
if u["id"] == user_id:
return u["name"]
return None
六、总结
Python异常处理是编写健壮程序的必备技能,其核心要点可总结为:
-
基础结构:
try-except捕获异常,else执行正常逻辑,finally释放资源,raise主动抛异常; -
精准捕获:优先捕获指定异常,避免裸
except,使用as关键字获取异常详情; -
进阶用法:自定义异常满足业务需求,异常链保留原始上下文,
traceback打印堆栈信息; -
实战原则:针对文件、网络、数据等场景做针对性处理,遵循“明确信息、自动释放、不吞异常”的最佳实践;
-
避坑要点:不滥用异常、不隐藏异常、不在
finally中返回值、不替代正常判断。
异常处理的终极目标,是让程序在面对意外时能够“优雅降级”,同时为开发者提供足够的上下文信息来排查问题。掌握本文的内容,你可以在实际项目中写出更稳定、更易维护的Python代码。
更多推荐


所有评论(0)