Python异常处理全面指南:构建健壮程序的关键技术
# 完整异常处理模板
def process_file(file_path):
"""文件处理示例函数"""
file = None
try:
file = open(file_path, 'r', encoding='utf-8')
data = json.load(file)
if not data:
raise EmptyDataError("文件内容为空") # 自定义异常
result = complex_processing(data) # 可能抛出多种异常
except FileNotFoundError as e:
logging.error(f"文件不存在: {e}")
raise # 重新抛出异常
except json.JSONDecodeError as e:
raise InvalidFormatError("JSON格式错误") from e
except EmptyDataError as e:
logging.warning(e)
return None
else:
logging.info("文件处理成功")
return result
finally:
if file is not None:
try:
file.close()
except IOError as e:
logging.error(f"关闭文件失败: {e}")
一、异常处理结构详解
- 完整结构流程图
开始
↓
try
├─ 可能抛出异常的代码
↓
except
├─ 捕获指定异常
↓
else (可选)
├─ 无异常时执行
↓
finally (可选)
└─ 始终执行的清理代码
- 执行顺序示例
def division_test(value):
try:
print("try块开始")
result = 10 / value
except ZeroDivisionError:
print("捕获除零错误")
except TypeError:
print("捕获类型错误")
else:
print("计算结果:", result)
finally:
print("finally块执行")
# 测试用例
division_test(2) # 正常执行
division_test(0) # 触发ZeroDivisionError
division_test("a") # 触发TypeError
- 各代码块使用场景
代码块 | 典型用途 | 最佳实践 |
---|---|---|
try | 包裹可能出错的代码 | 尽量缩小代码范围 |
except | 处理特定异常 | 明确指定异常类型,避免裸露except |
else | 执行依赖try成功的操作 | 将与try相关的后续操作放在else中 |
finally | 资源清理(文件/网络连接) | 确保关闭资源的代码必须放在此处 |
二、自定义异常与异常链
- 自定义异常类模板
class AppBaseError(Exception):
"""应用基础异常类"""
def __init__(self, message="应用错误", code=1000):
self.code = code
self.message = message
super().__init__(self.message)
class NetworkError(AppBaseError):
"""网络相关异常"""
def __init__(self, url, status_code):
super().__init__(
message=f"网络请求失败: {url} (状态码: {status_code})",
code=2001
)
self.url = url
self.status_code = status_code
class DatabaseError(AppBaseError):
"""数据库操作异常"""
ERROR_CODES = {
1452: "外键约束失败",
1062: "重复键值"
}
def __init__(self, sql, errcode):
code = 3000 + errcode
msg = f"SQL执行错误: {self.ERROR_CODES.get(errcode, '未知错误')}"
super().__init__(message=msg, code=code)
self.sql = sql
- 异常链实践
def fetch_data():
try:
response = requests.get('https://api.example.com/data', timeout=5)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
raise DataFetchError("数据获取失败") from e # 保留原始异常信息
try:
data = fetch_data()
except DataFetchError as e:
print(f"错误原因: {e}")
print(f"原始异常: {e.__cause__}") # 显示底层网络异常
三、断言与防御性编程
- 断言使用规范
def calculate_discount(price, discount):
"""计算商品折扣价"""
# 前置条件检查
assert isinstance(price, (int, float)), "价格必须是数值"
assert 0 <= discount <= 1, "折扣率应在0-1之间"
# 业务逻辑
discounted = price * (1 - discount)
# 后置条件验证
assert discounted <= price, "折后价不应超过原价"
return discounted
# 测试断言
try:
calculate_discount(100, 0.2) # 正常
calculate_discount("100", 0.5) # 触发类型断言
except AssertionError as e:
print(f"输入验证失败: {e}")
- 防御性编程技巧
def safe_divide(a, b):
"""安全的除法运算"""
# 输入验证
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
raise TypeError("操作数必须是数值类型")
# 边界条件检查
if b == 0:
raise ValueError("除数不能为零")
# 类型转换
result = float(a) / float(b)
# 结果验证
if math.isinf(result):
raise OverflowError("计算结果溢出")
return round(result, 4)
四、异常处理最佳实践
- 异常处理决策树
出现错误情况
↓
是否属于正常业务流程? → 是 → 使用返回码/状态标识
↓
否
↓
是否可恢复? → 是 → 捕获处理
↓
否
↓
抛出异常终止流程
- 异常日志规范
import logging
import traceback
logger = logging.getLogger(__name__)
try:
risky_operation()
except (NetworkError, DatabaseError) as e:
logger.error(f"操作失败 [代码:{e.code}]: {e.message}")
except Exception as e:
logger.critical(
f"未处理的异常: {str(e)}\n"
f"堆栈跟踪: {traceback.format_exc()}"
)
raise
- 性能优化建议
- 避免在频繁执行的代码路径中使用try块
- 将异常处理移到循环外部
- 使用预检查替代异常捕获(LBYL vs EAFP)
# 低效方式
for num in numbers:
try:
value = 10 / num
except ZeroDivisionError:
value = 0
# 高效方式
processed = []
for num in numbers:
if num == 0:
processed.append(0)
else:
processed.append(10/num)
异常处理反模式:
# 错误1:吞噬所有异常
try:
do_something()
except:
pass
# 错误2:过于宽泛的异常捕获
try:
process_data()
except Exception as e:
handle_error()
# 错误3:重复的异常处理
try:
file.read()
except IOError:
file.close()
try:
file.write()
except IOError:
file.close()
# 正确方式:使用上下文管理器
with open('file.txt') as f:
process(f)
调试建议:
# 在开发环境启用详细调试
if DEBUG:
import pdb
pdb.set_trace()
# 使用__debug__优化生产代码
if __debug__:
print("调试信息")
else:
logging.info("生产日志")