我来为你编写一篇关于Python错误与异常处理的学习教程,针对AI开发场景深入讲解。
Python错误与异常处理:AI开发者的实战指南
一、为什么错误处理对AI开发特别重要?
在AI开发中,代码不是运行在完美的实验室环境里,而是面对:
- 脏数据:缺失值、格式错误、超出范围的数据
- 模型不稳定:推理失败、内存溢出、显存不足
- 外部依赖:网络请求超时、API限流、文件不存在
一个没有错误处理的AI程序,就像没有安全带的赛车------速度快,但随时会崩溃。
python
# ❌ 没有错误处理的AI推理代码
def predict(image_path):
image = load_image(image_path) # 如果图片损坏呢?
tensor = preprocess(image) # 如果格式不对呢?
result = model(tensor) # 如果显存不够呢?
return result
# ✅ 有错误处理的版本
def predict_safe(image_path):
try:
image = load_image(image_path)
tensor = preprocess(image)
result = model(tensor)
return result
except FileNotFoundError:
return {"error": "图片文件不存在"}
except MemoryError:
return {"error": "显存不足,请降低batch size"}
except Exception as e:
return {"error": f"未知错误: {e}"}
二、错误 vs 异常:核心概念
2.1 语法错误(Syntax Error)
编译前就能发现,程序根本无法运行。
python
# ❌ 语法错误
def predict(
# 缺少括号或冒号
return x
# Python会直接报错,不会执行任何代码
# SyntaxError: invalid syntax
2.2 异常(Exception)
语法正确,但运行时出了问题。
python
# ✅ 语法正确,但运行时会抛异常
def divide(a, b):
return a / b
result = divide(10, 0) # ZeroDivisionError: division by zero
2.3 AI开发中最常见的异常类型
| 异常类型 | 触发场景 | AI开发示例 |
|---|---|---|
FileNotFoundError |
读取不存在的文件 | torch.load('model.pth') 模型文件不存在 |
ValueError |
值类型正确但内容无效 | 图片尺寸不对、标签超出范围 |
KeyError |
字典键不存在 | data['label'] 但数据中没有label字段 |
IndexError |
列表索引越界 | batch中第10个样本不存在 |
TypeError |
类型不匹配 | 把字符串传给需要tensor的函数 |
RuntimeError |
运行时通用错误 | CUDA out of memory |
TimeoutError |
操作超时 | API请求超时 |
ConnectionError |
网络问题 | 下载模型失败 |
三、Python异常处理基础
3.1 基本结构:try/except
python
try:
# 可能出错的代码
result = risky_operation()
except SomeException:
# 处理特定异常
handle_error()
实战示例:安全加载数据集
python
def load_dataset_safe(filepath):
try:
data = pd.read_csv(filepath)
print(f"成功加载 {len(data)} 条数据")
return data
except FileNotFoundError:
print(f"错误:文件 {filepath} 不存在")
return None
except pd.errors.EmptyDataError:
print("错误:文件为空")
return None
except Exception as e:
print(f"未知错误:{type(e).__name__}: {e}")
return None
3.2 捕获多个异常
方式一:多个except块
python
try:
model = torch.load('model.pth')
output = model(input_tensor)
except FileNotFoundError:
print("模型文件不存在,请检查路径")
except RuntimeError as e:
if "CUDA out of memory" in str(e):
print("显存不足,尝试减小batch size")
else:
print(f"运行时错误:{e}")
except Exception as e:
print(f"未预期的错误:{e}")
方式二:元组捕获
python
try:
value = int(user_input)
except (ValueError, TypeError):
# 同时处理多种异常
print("输入不是有效的数字")
3.3 else 和 finally
python
try:
model = load_model()
result = model.predict(data)
except FileNotFoundError:
print("模型加载失败")
result = None
else:
# 只有try块没有异常时才执行
print("推理成功,保存结果")
save_result(result)
finally:
# 无论是否异常都会执行
# 常用于清理资源
cleanup_gpu_memory()
print("清理完成")
四、AI开发中的高级错误处理模式
4.1 优雅降级(Graceful Degradation)
当某个组件失败时,系统不会完全崩溃,而是使用备用方案。
python
def get_embedding_with_fallback(text):
"""获取文本嵌入,支持多模型降级"""
# 主模型:OpenAI API
try:
response = openai.Embedding.create(
input=text,
model="text-embedding-3-small"
)
return response['data'][0]['embedding']
except (ConnectionError, TimeoutError):
print("API连接失败,降级到本地模型")
# 备用方案1:本地Sentence Transformers
try:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('all-MiniLM-L6-v2')
return model.encode(text).tolist()
except ImportError:
print("本地模型未安装,降级到随机嵌入")
# 最终备用:随机向量(保证不崩溃)
import numpy as np
return np.random.randn(384).tolist()
4.2 重试机制(Retry Logic)
网络请求、模型推理等操作,偶尔失败可以重试。
python
import time
from functools import wraps
def retry(max_attempts=3, delay=1, backoff=2):
"""重试装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
wait = delay
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
print(f"尝试 {attempt + 1}/{max_attempts} 失败: {e}")
if attempt < max_attempts - 1:
time.sleep(wait)
wait *= backoff # 指数退避
raise last_exception
return wrapper
return decorator
# 使用示例
@retry(max_attempts=3, delay=2)
def call_llm_api(prompt):
"""调用LLM API,自动重试"""
response = requests.post(
'https://api.openai.com/v1/completions',
json={'prompt': prompt},
timeout=10
)
response.raise_for_status()
return response.json()
4.3 数据验证与防御式编程
AI开发黄金法则:永远不要信任输入数据!
python
def safe_preprocess_image(image_data):
"""安全的图片预处理"""
# 检查输入类型
if not isinstance(image_data, (np.ndarray, torch.Tensor)):
raise TypeError(f"期望ndarray或Tensor,得到{type(image_data)}")
# 检查数据范围
if image_data.max() > 255:
print("警告:像素值超出0-255范围,进行裁剪")
image_data = np.clip(image_data, 0, 255)
# 检查shape
if len(image_data.shape) not in [2, 3]:
raise ValueError(f"图片维度错误,期望2D或3D,得到{len(image_data.shape)}D")
# 检查NaN/Inf
if np.isnan(image_data).any():
raise ValueError("图片包含NaN值")
if np.isinf(image_data).any():
raise ValueError("图片包含无穷值")
# 一切正常
return image_data / 255.0
4.4 自定义异常类
让错误类型更语义化,便于调试。
python
class AIPipelineError(Exception):
"""AI流程基类异常"""
pass
class ModelLoadError(AIPipelineError):
"""模型加载失败"""
pass
class DataValidationError(AIPipelineError):
"""数据验证失败"""
pass
class InferenceTimeoutError(AIPipelineError):
"""推理超时"""
pass
# 使用自定义异常
def run_inference_pipeline(model_path, input_data):
try:
if not os.path.exists(model_path):
raise ModelLoadError(f"模型文件不存在: {model_path}")
if not validate_input(input_data):
raise DataValidationError("输入数据格式无效")
result = model.predict(input_data)
return result
except ModelLoadError as e:
print(f"[模型错误] {e}")
# 触发模型下载流程
download_model(model_path)
except DataValidationError as e:
print(f"[数据错误] {e}")
# 尝试修复数据
input_data = repair_data(input_data)
except InferenceTimeoutError as e:
print(f"[超时错误] {e}")
# 切换到更快的模型
use_fast_model()
五、实战案例:完整的AI推理管道
python
import logging
import sys
from contextlib import contextmanager
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@contextmanager
def managed_resources():
"""资源管理器,确保GPU内存释放"""
try:
yield
finally:
# 清理CUDA缓存
import torch
if torch.cuda.is_available():
torch.cuda.empty_cache()
logger.info("GPU缓存已清理")
class SafeInferencePipeline:
"""带完整错误处理的推理管道"""
def __init__(self, model_path, device='cuda'):
self.model_path = model_path
self.device = device
self.model = None
def load_model(self):
"""安全加载模型"""
try:
import torch
self.model = torch.load(self.model_path, map_location=self.device)
self.model.eval()
logger.info(f"模型加载成功: {self.model_path}")
return True
except FileNotFoundError:
logger.error(f"模型文件不存在: {self.model_path}")
return False
except RuntimeError as e:
if "out of memory" in str(e).lower():
logger.error(f"显存不足,尝试使用CPU")
self.device = 'cpu'
self.model = torch.load(self.model_path, map_location='cpu')
return True
raise
except Exception as e:
logger.error(f"模型加载失败: {type(e).__name__}: {e}")
return False
def predict(self, input_data, timeout=30):
"""安全推理"""
if self.model is None:
raise RuntimeError("模型未加载,请先调用load_model()")
with managed_resources():
try:
# 输入验证
if input_data is None:
raise ValueError("输入数据为空")
# 类型转换(如果需要)
import torch
if not isinstance(input_data, torch.Tensor):
input_data = torch.tensor(input_data)
# 推理(带超时)
import signal
def timeout_handler(signum, frame):
raise TimeoutError("推理超时")
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(timeout)
try:
with torch.no_grad():
output = self.model(input_data.to(self.device))
signal.alarm(0) # 取消超时
return output
except TimeoutError:
logger.error("推理超时")
return None
except torch.cuda.OutOfMemoryError:
logger.error("CUDA显存不足")
# 尝试CPU推理
with torch.no_grad():
return self.model(input_data.cpu())
except Exception as e:
logger.error(f"推理失败: {type(e).__name__}: {e}")
return None
# 使用示例
def main():
pipeline = SafeInferencePipeline("model.pth", device='cuda')
if not pipeline.load_model():
logger.error("无法继续,模型加载失败")
sys.exit(1)
# 批量处理数据
test_data = [np.random.randn(3, 224, 224) for _ in range(10)]
results = []
for i, data in enumerate(test_data):
try:
result = pipeline.predict(data, timeout=5)
if result is not None:
results.append(result)
logger.info(f"样本{i}推理成功")
else:
logger.warning(f"样本{i}推理失败,跳过")
except Exception as e:
logger.error(f"样本{i}处理异常: {e}")
continue
logger.info(f"完成:成功{len(results)}/{len(test_data)}个样本")
# 清理
del pipeline
import torch
torch.cuda.empty_cache()
if __name__ == "__main__":
main()
六、最佳实践总结
✅ 要做:
- 捕获具体异常 :
except ValueError而不是except: - 记录详细日志:错误发生时记录上下文
- 优雅降级:主方案失败时使用备用方案
- 资源清理 :使用
finally或上下文管理器 - 输入验证:永远不要信任外部数据
❌ 不要做:
- 空except块 :
except:会隐藏所有错误 - 吞掉异常:只记录日志但不处理
- 过度try/except:只在预期会出错的地方使用
- 忽略异常链 :使用
raise ... from ...保留原始信息
python
# ❌ 错误示例
try:
result = dangerous()
except:
pass # 吞掉错误,非常危险!
# ✅ 正确示例
try:
result = dangerous()
except SpecificError as e:
logger.error(f"处理失败: {e}")
result = default_value
我来为你的教程添加一个完整的Python异常分类总结表格:
七、Python异常完整分类手册
七.1、内置异常层级结构
python
BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
└── Exception
├── StopIteration
├── StopAsyncIteration
├── ArithmeticError
│ ├── FloatingPointError
│ ├── OverflowError
│ └── ZeroDivisionError
├── AssertionError
├── AttributeError
├── BufferError
├── EOFError
├── ImportError
│ └── ModuleNotFoundError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── MemoryError
├── NameError
│ └── UnboundLocalError
├── OSError
│ ├── BlockingIOError
│ ├── ChildProcessError
│ ├── ConnectionError
│ │ ├── BrokenPipeError
│ │ ├── ConnectionAbortedError
│ │ ├── ConnectionRefusedError
│ │ └── ConnectionResetError
│ ├── FileExistsError
│ ├── FileNotFoundError
│ ├── InterruptedError
│ ├── IsADirectoryError
│ ├── NotADirectoryError
│ ├── PermissionError
│ ├── ProcessLookupError
│ └── TimeoutError
├── ReferenceError
├── RuntimeError
│ ├── NotImplementedError
│ └── RecursionError
├── SyntaxError
│ └── IndentationError
│ └── TabError
├── SystemError
├── TypeError
├── ValueError
│ └── UnicodeError
│ ├── UnicodeDecodeError
│ ├── UnicodeEncodeError
│ └── UnicodeTranslateError
└── Warning
└── ...
七.2、按场景分类的异常速查表
📁 文件与IO操作异常
| 异常类型 | 触发条件 | AI开发场景 | 处理示例 |
|---|---|---|---|
FileNotFoundError |
文件/目录不存在 | 加载模型权重、读取数据集 | try: torch.load('model.pth') |
PermissionError |
权限不足 | 写入日志、保存模型 | except: 切换到临时目录 |
IsADirectoryError |
期望文件但得到目录 | 读取配置文件 | except: 检查路径类型 |
NotADirectoryError |
期望目录但得到文件 | 创建数据集缓存目录 | except: 先删除文件再创建 |
FileExistsError |
文件已存在且禁止覆盖 | 保存训练checkpoint | except: 自动重命名或询问 |
EOFError |
读取到文件末尾 | 读取pickle文件 | except: 文件可能损坏 |
TimeoutError |
IO操作超时 | 下载大模型文件 | except: 重试或使用断点续传 |
🔢 数值计算异常
| 异常类型 | 触发条件 | AI开发场景 | 处理示例 |
|---|---|---|---|
ZeroDivisionError |
除以0 | 计算准确率、归一化 | if denominator == 0: return 0 |
OverflowError |
数值过大溢出 | 指数运算、大整数 | except: 使用对数空间计算 |
FloatingPointError |
浮点运算异常 | 科学计算(需启用) | except: 检查数值稳定性 |
ValueError |
值无效但类型正确 | softmax负数、负的sqrt | except: 数值裁剪或归一化 |
ArithmeticError |
算术错误基类 | 捕获所有算术错误 | except ArithmeticError: |
📊 数据结构异常
| 异常类型 | 触发条件 | AI开发场景 | 处理示例 |
|---|---|---|---|
KeyError |
字典键不存在 | 访问config['learning_rate'] | config.get('lr', 0.001) |
IndexError |
列表索引越界 | 批次采样、序列切片 | if idx < len(data): |
TypeError |
类型不匹配 | 混用numpy和tensor | tensor = torch.tensor(array) |
AttributeError |
对象无该属性 | 调用不存在的方法 | if hasattr(obj, 'method'): |
LookupError |
索引/键错误基类 | 捕获索引和键错误 | except LookupError: |
💾 内存与资源异常
| 异常类型 | 触发条件 | AI开发场景 | 处理示例 |
|---|---|---|---|
MemoryError |
内存不足 | 加载大数据集、大batch | except: 使用数据生成器 |
RuntimeError |
运行时通用错误 | CUDA OOM、梯度爆炸 | except: 降低batch size |
RecursionError |
递归过深 | 递归数据预处理 | except: 改用迭代实现 |
ReferenceError |
访问已回收对象 | 弱引用使用不当 | except: 检查对象生命周期 |
🌐 网络与连接异常
| 异常类型 | 触发条件 | AI开发场景 | 处理示例 |
|---|---|---|---|
ConnectionError |
网络连接问题 | API调用、模型下载 | except: 重试+指数退避 |
ConnectionRefusedError |
目标拒绝连接 | 连接推理服务 | except: 检查服务状态 |
ConnectionResetError |
连接被重置 | 长时间推理 | except: 重新建立连接 |
BrokenPipeError |
管道损坏 | 进程间通信 | except: 重启子进程 |
TimeoutError |
操作超时 | 推理超时、下载超时 | except: 降级或取消 |
📦 模块与导入异常
| 异常类型 | 触发条件 | AI开发场景 | 处理示例 |
|---|---|---|---|
ImportError |
模块导入失败 | 可选依赖(如tensorflow) | except: 提示安装或降级 |
ModuleNotFoundError |
模块不存在 | 动态导入 | except: pip install package |
SyntaxError |
代码语法错误 | eval执行字符串 | except: 检查代码格式 |
IndentationError |
缩进错误 | 动态生成代码 | except: 修复缩进 |
🎯 AI开发专属异常模式
| 场景 | 常见异常 | 标准处理策略 |
|---|---|---|
| 模型加载 | FileNotFoundError, RuntimeError, MemoryError |
1. 检查路径 2. 降级到CPU 3. 重新下载 |
| 数据预处理 | ValueError, TypeError, KeyError |
1. 数据验证 2. 缺失值填充 3. 格式转换 |
| 模型推理 | RuntimeError(CUDA OOM), TimeoutError |
1. 动态batch 2. CPU fallback 3. 超时重试 |
| 训练循环 | ZeroDivisionError, OverflowError |
1. 梯度裁剪 2. 学习率调整 3. NaN检测 |
| API调用 | ConnectionError, TimeoutError, JSONDecodeError |
1. 重试机制 2. 本地缓存 3. 降级服务 |
| 数据加载 | StopIteration, EOFError, KeyError |
1. 数据增强 2. 循环采样 3. 默认值 |
七.3、快速决策表:我应该捕获哪个异常?
python
# 场景1: 文件操作
try:
with open('data.csv') as f:
data = pd.read_csv(f)
except FileNotFoundError:
# 文件不存在 → 创建默认文件或使用备用数据
except PermissionError:
# 权限问题 → 切换目录或请求权限
except pd.errors.EmptyDataError:
# 文件为空 → 使用默认数据
# 场景2: 数值计算
try:
accuracy = correct / total
except ZeroDivisionError:
accuracy = 0.0 # 没有样本时准确率为0
# 场景3: 字典/列表访问
try:
lr = config['training']['learning_rate']
except KeyError:
lr = 0.001 # 使用默认学习率
# 场景4: 类型转换
try:
value = float(user_input)
except (ValueError, TypeError):
value = 0.0 # 转换失败使用默认值
# 场景5: 网络请求
for attempt in range(3):
try:
response = requests.get(url, timeout=5)
break
except (ConnectionError, TimeoutError) as e:
if attempt == 2:
raise
time.sleep(2 ** attempt) # 指数退避
# 场景6: GPU内存不足
try:
output = model(input_tensor.cuda())
except RuntimeError as e:
if "CUDA out of memory" in str(e):
# 降级到CPU
output = model(input_tensor.cpu())
else:
raise
七.4、异常处理反模式警示表
| ❌ 错误写法 | 为什么危险 | ✅ 正确写法 |
|---|---|---|
except: |
捕获包括SystemExit在内的所有异常 |
except Exception: |
except Exception as e: pass |
吞掉错误,调试困难 | 记录日志或重新抛出 |
except (ValueError,): |
多余的逗号创建元组 | except ValueError: |
raise e |
丢失原始调用栈 | raise 或 raise ... from e |
except: return None |
隐藏错误原因 | 返回错误对象或重新抛出 |
七.5、调试技巧:如何确定捕获什么异常?
python
# 技巧1: 先让程序崩溃,看异常类型
def debug_predict(data):
return model(data) # 让它崩溃,看控制台输出
# 技巧2: 使用sys.exc_info()获取详细信息
import sys
try:
risky_call()
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
print(f"异常类型: {exc_type.__name__}")
print(f"异常信息: {exc_value}")
# 技巧3: 使用traceback打印完整堆栈
import traceback
try:
risky_call()
except Exception as e:
traceback.print_exc() # 打印完整调用栈
七.6、记忆口诀
文件IO找FileNotFound和Permission
数值计算防ZeroDivision和Overflow
字典列表用KeyError和IndexError
网络请求抓Connection和Timeout
GPU内存要RuntimeError来判断
万能except绝不用,具体类型才安全
八、练习任务
- 基础练习:写一个函数,安全地将字符串列表转换为浮点数列表,处理转换失败的情况
- 进阶练习:为你的数据加载器添加重试机制和优雅降级
- 项目练习:为现有的AI推理代码添加完整的异常处理,包括日志记录和资源清理