飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 第1章 Python简介

    • Python是什么
    • 安装与运行
    • 交互式解释器
    • 注释与编码规范
  • 第2章 变量与数据类型

    • 变量与对象
    • 整数 int
    • 浮点数 float
    • 复数 complex
    • 布尔值 bool
    • 字符串 str
    • 空值 None
    • 类型转换
  • 第3章 运算符与表达式

    • 算术运算符
    • 比较运算符
    • 赋值运算符
    • 逻辑运算符
    • 位运算符
    • 身份与成员运算符
    • 海象运算符
    • 运算符优先级
  • 第4章 流程控制

    • if 语句
    • if-else 语句
    • if-elif-else 语句
    • match-case 语句
    • 条件表达式(三元运算符)
    • while 循环
    • for 循环
    • range 函数
    • break 与 continue
    • 循环的 else 子句
    • pass 语句
  • 第5章 数据结构

    • 列表创建与索引
    • 列表方法
    • 列表推导式
    • 元组
    • 序列解包
    • 集合
    • 字典创建与访问
    • 字典方法
    • 字典推导式
    • range 对象
  • 第6章 函数

    • 定义函数
    • 位置参数与关键字参数
    • 默认参数
    • 可变参数
    • 解包实参
    • 函数返回值
    • lambda 表达式
    • 文档字符串与注解
    • 作用域与命名空间
    • global 与 nonlocal
  • 第7章 模块与包

    • 模块导入
    • 模块搜索路径
    • 包与相对导入
    • 标准库概览
  • 第8章 文件与输入输出

    • 文件读写
    • 上下文管理器
    • 字符串格式化
    • JSON 与 CSV
  • 第9章 面向对象

    • 类与对象
    • 方法
    • 实例变量与类变量
    • 私有变量
    • 继承
    • 多重继承
    • 魔术方法
    • 属性装饰器
    • 数据类 dataclass
  • 第10章 异常处理

    • 语法错误与异常
    • try-except
    • 异常链与 raise
    • 清理操作
    • 自定义异常
  • 第11章 迭代器与生成器

    • 迭代器协议
    • 生成器
    • 生成器表达式
    • 迭代工具
  • 第12章 高级特性

    • 装饰器
    • 函数式编程
  • 第13章 工程实践

    • 测试与调试
    • 代码质量
    • 虚拟环境

自定义异常

当内置异常无法准确表达程序特定的错误语义时,可以通过继承 Exception 创建自定义异常类。良好的自定义异常体系能让错误信息更专业、捕获更精准、调试更高效。

继承 Exception 创建异常类

自定义异常类只需继承 Exception(或其子类),通常保持简单,仅提供必要的属性和文档字符串。

class ValidationError(Exception):
    """数据校验失败时抛出。"""
    pass

class ConfigError(Exception):
    """配置文件相关错误。"""
    pass

# 使用
raise ValidationError("用户名字段不能为空")

异常类命名应遵循标准库的惯例,以 Error 结尾(如 ValueError、TypeError),让人一眼识别其用途。虽然异常类可以做任何普通类能做的事,但过度复杂的设计往往适得其反——异常的核心职责是携带错误信息并支持被捕获。

自定义异常参数

通过重写 __init__() 和 __str__(),可以让异常携带结构化数据,并在打印时显示更友好的消息。

class APIError(Exception):
    """HTTP API 请求错误。"""
    
    def __init__(self, status_code, message, endpoint):
        self.status_code = status_code
        self.endpoint = endpoint
        super().__init__(message)
    
    def __str__(self):
        return f"[{self.status_code}] {self.args[0]} (URL: {self.endpoint})"

# 使用
try:
    raise APIError(404, "资源不存在", "/api/users/99")
except APIError as e:
    print(e)              # [404] 资源不存在 (URL: /api/users/99)
    print(e.status_code)  # 404
    print(e.endpoint)      # /api/users/99

注意在自定义 __init__() 时应当调用 super().__init__() 传入消息字符串,这样 .args 属性和默认的 __str__() 行为才能正常工作。

class FieldError(Exception):
    """表单字段错误。"""
    
    def __init__(self, field_name, value, reason):
        self.field_name = field_name
        self.value = value
        self.reason = reason
        super().__init__(f"字段 '{field_name}' 不合法:{reason}")

try:
    raise FieldError("age", "abc", "必须是整数")
except FieldError as e:
    print(e)            # 字段 'age' 不合法:必须是整数
    print(e.field_name) # age
    print(e.value)      # abc

异常层次设计

为项目设计分层的异常体系,可以实现精准捕获和统一处理。顶层异常作为命名空间,让调用者选择捕获整个模块的错误或只捕获特定类型。

# 项目根异常
class ProjectError(Exception):
    """本项目所有异常的基类。"""
    pass

# 模块级异常
class DataError(ProjectError):
    """数据层错误。"""
    pass

class ServiceError(ProjectError):
    """业务层错误。"""
    pass

# 具体异常
class RecordNotFoundError(DataError):
    """数据库记录不存在。"""
    def __init__(self, table, record_id):
        self.table = table
        self.record_id = record_id
        super().__init__(f"表 {table} 中找不到 ID={record_id} 的记录")

class PermissionDeniedError(ServiceError):
    """权限不足。"""
    pass

class RateLimitError(ServiceError):
    """请求频率超限。"""
    def __init__(self, retry_after):
        self.retry_after = retry_after
        super().__init__(f"请求过于频繁,请 {retry_after} 秒后重试")

这种层次结构的好处在于调用者可以根据需要选择捕获粒度:

# 捕获所有项目异常
try:
    process_request()
except ProjectError as e:
    print(f"业务错误:{e}")

# 只捕获数据层异常
try:
    fetch_data()
except DataError as e:
    print(f"数据错误:{e}")

# 只捕获特定异常
try:
    get_user(99)
except RecordNotFoundError as e:
    print(f"记录不存在:{e.table}.{e.record_id}")

异常组 ExceptionGroup(Python 3.11+)

传统 raise 一次只能抛出一个异常。当批量任务中多个步骤同时失败时,需要一种机制来报告多个不相关的异常。ExceptionGroup 打包了一个异常实例列表,使它们可以一起被抛出和捕获。

def validate_batch(data):
    errors = []
    for item in data:
        try:
            process(item)
        except Exception as e:
            errors.append(e)
    if errors:
        raise ExceptionGroup("批量处理失败", errors)

data = [1, "bad", 3, None, 5]
try:
    validate_batch(data)
except ExceptionGroup as eg:
    print(f"捕获到异常组:{eg.message}")
    for exc in eg.exceptions:
        print(f"  - {type(exc).__name__}: {exc}")

使用 except* 语法可以有选择地只处理组中符合某种类型的异常。每个 except* 子句从组中提取匹配的异常,让其他异常继续传播或被后续子句处理。

def failing():
    raise ExceptionGroup("group1", [
        OSError(1),
        SystemError(2),
        ExceptionGroup("group2", [
            OSError(3),
            RecursionError(4)
        ])
    ])

try:
    failing()
except* OSError as eg:
    print(f"OS 错误数量:{len(eg.exceptions)}")
except* SystemError as eg:
    print(f"系统错误数量:{len(eg.exceptions)}")
# 剩余的 RecursionError 会被重新抛出

except* 与普通 except 的关键区别在于:普通 except 捕获整个 ExceptionGroup 对象,而 except* 会拆分异常组,只提取匹配类型的子异常。嵌套在异常组中的必须是异常实例,而不是类型——因为这些异常通常是程序已经捕获并收集的实例。

用 add_note 添加上下文(Python 3.11+)

异常实例的 add_note(note) 方法允许在异常被捕获后追加注释信息。标准 Traceback 会在异常消息之后按添加顺序显示所有注释。

try:
    raise TypeError("bad type")
except Exception as e:
    e.add_note("发生在用户注册流程中")
    e.add_note("相关用户ID:user_42")
    raise

这在批量处理场景中特别有用:为每个子异常添加迭代上下文,帮助定位问题。

excs = []
for i, task in enumerate(tasks):
    try:
        run(task)
    except Exception as e:
        e.add_note(f"发生在第 {i+1} 个任务")
        excs.append(e)

if excs:
    raise ExceptionGroup("任务执行失败", excs)

自定义异常的最佳实践

  1. 始终继承 Exception(或更具体的内置异常),不要直接继承 BaseException,以免意外捕获系统退出信号。
  2. 提供清晰的文档字符串,说明异常的触发场景。
  3. 在 __init__ 中调用 super().__init__(),确保 .args 和字符串转换正常工作。
  4. 设计层次结构时遵循"宽基窄叶"原则:顶层宽泛,叶子节点具体,便于调用者按需捕获。
  5. 不要滥用异常做流程控制:异常应当用于"意外情况",而非预期的分支逻辑。
上一页
清理操作