错误、调试和测试

Python 错误处理与调试指南

1. 错误类型概述

在程序开发过程中,我们会遇到三类主要问题:

1.1 程序错误 (Bugs)

  • 定义:由于代码逻辑或实现错误导致的程序异常行为
  • 特点
    • 必须修复才能保证程序正确运行
    • 示例:类型错误、逻辑错误、边界条件处理不当等
  • 现代解决方案
    • 使用类型提示 (Python 3.5+)
    • 静态类型检查工具 (如 mypy)
    • 代码审查和配对编程
# 类型提示示例 (Python 3.5+)
def add_numbers(a: int, b: int) -> int:
    return a + b

1.2 用户输入错误

  • 定义:由用户提供的不符合预期的输入导致的错误
  • 特点
    • 可以通过输入验证预防
    • 示例:空输入、格式错误、超出范围的值等
  • 现代解决方案
    • 使用 Pydantic 等数据验证库
    • 正则表达式验证
    • 输入清理函数
# 使用 Pydantic 验证输入
from pydantic import BaseModel, EmailStr, ValidationError

class UserInput(BaseModel):
    email: EmailStr
    age: int

try:
    user = UserInput(email="test@example.com", age=30)
except ValidationError as e:
    print(f"输入验证失败: {e}")

1.3 运行时异常

  • 定义:程序运行期间无法预测的外部环境问题
  • 特点
    • 必须处理否则程序会崩溃
    • 示例:文件不存在、网络中断、内存不足等
  • 现代解决方案
    • 使用上下文管理器 (with 语句)
    • 异步异常处理
    • 重试机制 (如 tenacity 库)
# 使用上下文管理器处理文件操作
try:
    with open('file.txt', 'r') as f:
        content = f.read()
except FileNotFoundError:
    print("文件不存在")
except IOError:
    print("文件读取错误")

2. Python 异常处理机制

2.1 基本 try-except 结构

try:
    # 可能引发异常的代码
    result = 10 / 0
except ZeroDivisionError:
    # 异常处理代码
    print("不能除以零")

2.2 异常处理最佳实践

  1. 精确捕获异常:避免使用裸 except 语句
  2. 异常链:Python 3.3+ 支持 raise ... from ... 语法
  3. 资源清理:使用 finally 或上下文管理器
  4. 日志记录:使用 logging 模块记录异常
import logging

logging.basicConfig(level=logging.ERROR)

try:
    # 业务代码
    pass
except (ValueError, TypeError) as e:
    logging.error(f"输入值错误: {e}", exc_info=True)
    raise RuntimeError("处理失败") from e
finally:
    # 清理资源
    pass

2.3 自定义异常

class MyAppError(Exception):
    """应用基础异常"""
    
    def __init__(self, message, code):
        super().__init__(message)
        self.code = code

class InvalidInputError(MyAppError):
    """输入无效异常"""
    pass

try:
    raise InvalidInputError("无效的用户输入", code=400)
except MyAppError as e:
    print(f"错误代码 {e.code}: {e}")

3. 调试技术

3.1 使用 pdb 调试器

import pdb

def buggy_function(x):
    pdb.set_trace()  # 设置断点
    return x * 2 + 1

buggy_function(5)

常用 pdb 命令

  • n(ext) - 执行下一行
  • s(tep) - 进入函数调用
  • c(ontinue) - 继续执行直到下一个断点
  • l(ist) - 显示当前代码
  • p(rint) - 打印表达式值
  • q(uit) - 退出调试器

3.2 现代调试工具

  1. IDE 集成调试器 (VS Code, PyCharm)
  2. ipdb - 增强版 pdb
  3. PuDB - 基于终端的可视化调试器
  4. 远程调试 (使用 debugpy)
# 使用 ipdb
import ipdb; ipdb.set_trace()

4. 测试实践

4.1 单元测试 (unittest)

import unittest

def add(a, b):
    return a + b

class TestMath(unittest.TestCase):
    def test_add(self):
        self.assertEqual(add(2, 3), 5)
        self.assertEqual(add(-1, 1), 0)
        with self.assertRaises(TypeError):
            add("2", 3)

if __name__ == '__main__':
    unittest.main()

4.2 现代测试工具

  1. pytest - 更简洁的测试框架
  2. hypothesis - 基于属性的测试
  3. tox - 多环境测试
  4. coverage.py - 测试覆盖率分析
# pytest 示例
def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0

def test_add_type_error():
    with pytest.raises(TypeError):
        add("2", 3)

4.3 测试驱动开发 (TDD) 流程

  1. 编写失败的测试
  2. 实现最小功能使测试通过
  3. 重构代码
  4. 重复上述步骤

5. 错误处理与调试的最佳实践

  1. 防御性编程:验证输入,处理边界条件
  2. 尽早失败:发现问题立即报告
  3. 详细日志:记录足够多的调试信息
  4. 监控和告警:生产环境错误监控
  5. 文档化错误:记录已知错误和解决方案
# 结构化错误处理示例
def process_data(data):
    if not data:
        raise ValueError("数据不能为空")
    
    try:
        # 处理数据
        result = complex_operation(data)
    except DatabaseError as e:
        logging.error(f"数据库错误: {e}")
        raise ServiceUnavailable("暂时无法处理请求") from e
    except Exception as e:
        logging.error(f"处理数据时发生意外错误: {e}", exc_info=True)
        raise
    else:
        logging.info(f"成功处理数据: {result}")
        return result

通过结合现代 Python 工具和实践,可以显著提高代码的健壮性和可维护性,减少生产环境中的问题。