01-编程基础与数学基石:Python错误与异常处理

我来为你编写一篇关于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()

六、最佳实践总结

✅ 要做:

  1. 捕获具体异常except ValueError 而不是 except:
  2. 记录详细日志:错误发生时记录上下文
  3. 优雅降级:主方案失败时使用备用方案
  4. 资源清理 :使用 finally 或上下文管理器
  5. 输入验证:永远不要信任外部数据

❌ 不要做:

  1. 空except块except: 会隐藏所有错误
  2. 吞掉异常:只记录日志但不处理
  3. 过度try/except:只在预期会出错的地方使用
  4. 忽略异常链 :使用 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 丢失原始调用栈 raiseraise ... 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绝不用,具体类型才安全

八、练习任务

  1. 基础练习:写一个函数,安全地将字符串列表转换为浮点数列表,处理转换失败的情况
  2. 进阶练习:为你的数据加载器添加重试机制和优雅降级
  3. 项目练习:为现有的AI推理代码添加完整的异常处理,包括日志记录和资源清理
相关推荐
2401_835956811 小时前
mysql处理大量更新场景_InnoDB MVCC与MyISAM对比
jvm·数据库·python
m0_748920362 小时前
Oracle默认端口被占用如何连接_修改端口号操作教程
jvm·数据库·python
YummyJacky2 小时前
Hermes Agent自进化的实现方式
人工智能·python
qq_342295822 小时前
Redis怎样按照距离远近排序展示_通过GEORADIUS的ASC参数进行Geo排序
jvm·数据库·python
2201_761040592 小时前
C#比较两个二进制文件的差异 C#如何实现一个二进制diff工具
jvm·数据库·python
普鲁夕格2 小时前
【AI翻唱】RVC和SVC声音音色模型难找?推荐这个下载网站
人工智能
Csvn2 小时前
🌟 LangChain 30 天保姆级教程 · Day 23|Agent 进阶实战!Function Calling + 自动 Tool 注册,打造会“动
python·langchain
Csvn2 小时前
🌟 LangChain 30 天保姆级教程 · Day 22|长文档处理三剑客!MapReduce、Refine、Map-Rerank,让 AI 消化整本手册
python·langchain
皮卡蛋炒饭.2 小时前
线程的概念和控制
java·开发语言·jvm