📚 今日主题:异常处理与错误调试
一、为什么需要异常处理?
在程序运行过程中,错误是不可避免的:文件可能不存在、网络可能超时、用户输入可能无效。如果没有妥善处理,程序会直接崩溃。Python 的异常处理机制让我们能够优雅地应对错误,保证程序的健壮性。
ini
# ❌ 没有异常处理 - 程序会崩溃
def read_file_bad(filename):
f = open(filename, 'r')
content = f.read()
f.close()
return content
# 如果文件不存在,直接抛出 FileNotFoundError
# read_file_bad('not_exist.txt') # 程序终止!
二、try-except 基础用法
2.1 基本结构
python
def read_file_safe(filename):
try:
f = open(filename, 'r', encoding='utf-8')
content = f.read()
f.close()
return content
except FileNotFoundError:
print(f"错误:文件 '{filename}' 不存在")
return None
except PermissionError:
print(f"错误:没有权限读取 '{filename}'")
return None
except Exception as e:
print(f"发生未知错误:{e}")
return None
# 测试
result = read_file_safe('not_exist.txt')
print(f"返回结果:{result}") # 返回 None,程序继续运行
2.2 捕获多个异常
python
def safe_divide(a, b):
try:
result = a / b
return result
except (ZeroDivisionError, TypeError) as e:
print(f"计算错误:{e}")
return None
print(safe_divide(10, 2)) # 5.0
print(safe_divide(10, 0)) # None,捕获 ZeroDivisionError
print(safe_divide(10, 'a')) # None,捕获 TypeError
三、else 和 finally 子句
python
def process_data(data):
try:
result = int(data)
print(f"转换成功:{result}")
except ValueError:
print("转换失败:输入不是有效数字")
return None
else:
# 没有异常时执行
print("✓ 数据验证通过")
return result * 2
finally:
# 无论是否异常都执行(常用于清理资源)
print("--- 处理结束 ---")
process_data("42")
# 输出:
# 转换成功:42
# ✓ 数据验证通过
# --- 处理结束 ---
process_data("abc")
# 输出:
# 转换失败:输入不是有效数字
# --- 处理结束 ---
四、主动抛出异常
python
def validate_age(age):
if age < 0:
raise ValueError("年龄不能为负数")
if age > 150:
raise ValueError("年龄不能超过 150 岁")
if not isinstance(age, int):
raise TypeError("年龄必须是整数")
return f"年龄验证通过:{age}岁"
# 测试
try:
print(validate_age(25)) # ✓ 年龄验证通过:25 岁
print(validate_age(-5)) # 抛出 ValueError
except ValueError as e:
print(f"验证失败:{e}")
五、自定义异常类
python
class InsufficientFundsError(Exception):
"""余额不足异常"""
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(f"余额不足:当前余额 {balance},需要 {amount}")
class BankAccount:
def __init__(self, initial_balance=0):
self.balance = initial_balance
def withdraw(self, amount):
if amount > self.balance:
raise InsufficientFundsError(self.balance, amount)
self.balance -= amount
return self.balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("存款金额必须大于 0")
self.balance += amount
return self.balance
# 测试
account = BankAccount(100)
try:
account.withdraw(150) # 触发自定义异常
except InsufficientFundsError as e:
print(f"取款失败:{e}")
print(f"建议:请先存款至少 {e.amount - e.balance} 元")
六、实用的调试技巧
6.1 记录异常堆栈
python
import traceback
import logging
# 配置日志
logging.basicConfig(
level=logging.ERROR,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def risky_operation():
try:
# 模拟可能出错的操作
data = [1, 2, 3]
return data[10] # IndexError
except Exception as e:
# 记录完整堆栈信息
logging.error(f"操作失败:{e}")
logging.error(traceback.format_exc())
raise
# 调用后会看到完整的错误堆栈
6.2 上下文管理器处理资源
python
from contextlib import contextmanager
@contextmanager
def open_file_safe(filename, mode='r'):
f = None
try:
f = open(filename, mode, encoding='utf-8')
yield f
except Exception as e:
print(f"文件操作出错:{e}")
raise
finally:
if f:
f.close()
print("文件已关闭")
# 使用
with open_file_safe('test.txt', 'w') as f:
f.write('Hello World')
# 自动关闭文件,即使发生异常
七、最佳实践总结
| ✅ 推荐做法 | ❌ 避免做法 |
|---|---|
| 捕获具体异常类型 | except: 捕获所有异常 |
使用 finally 清理资源 |
忘记关闭文件/连接 |
| 记录异常堆栈便于调试 | 静默吞掉异常 |
| 自定义异常表达业务错误 | 滥用通用 Exception |
| 尽早抛出,晚些捕获 | 过度嵌套 try-except |
八、实战练习
python
# 练习:实现一个安全的 JSON 配置加载器
import json
def load_config(filepath):
"""
安全加载 JSON 配置文件
要求:
1. 文件不存在时返回默认配置
2. JSON 格式错误时记录日志并返回默认配置
3. 成功时返回解析后的字典
"""
default_config = {
"debug": False,
"max_connections": 100,
"timeout": 30
}
try:
with open(filepath, 'r', encoding='utf-8') as f:
config = json.load(f)
print(f"✓ 成功加载配置:{filepath}")
return config
except FileNotFoundError:
print(f"⚠ 配置文件不存在,使用默认配置")
return default_config
except json.JSONDecodeError as e:
print(f"⚠ JSON 格式错误:{e},使用默认配置")
return default_config
except Exception as e:
print(f"⚠ 未知错误:{e},使用默认配置")
return default_config
# 测试
config = load_config('config.json')
print(f"当前配置:{config}")
📝 今日要点
- 异常处理是健壮程序的基石 - 不要让用户看到崩溃的堆栈
- 精准捕获 - 只捕获你预期并能处理的异常
- 资源清理 - 用
finally或上下文管理器确保资源释放 - 主动抛出 - 用异常表达业务逻辑错误
- 记录日志 - 生产环境一定要记录异常堆栈