跳转至

Python 异常类怎么自定义,什么时候该用自定义异常?

Python 中如何自定义异常类?什么时候该用自定义异常而不用内置异常?

这题考察两点:

  • 写一个自定义异常类有多简单(语法)

  • 能说出什么时候必须“造轮子”(设计意识)


🧱 自定义异常:语法上极其简单

Python 里自定义异常,只需要继承 Exception(或它的子类)。 最简形式一行代码搞定:

class OrderNotFoundError(Exception):
    """订单不存在时抛出的异常"""
    pass

然后就可以像内置异常一样使用:

def get_order(order_id):
    order = db.find(order_id)
    if not order:
        raise OrderNotFoundError(f"订单 {order_id} 不存在")

✅ 这就完成了一个自定义异常,它和 ValueErrorRuntimeError 是同一个家族的。


🧩 如果只是这样,为什么要自定义?

因为名字就是语义。看两段代码对比:

❌ 只用内置异常

def cancel_order(order_id):
    order = get_order(order_id)
    if order.status == "shipped":
        raise RuntimeError("已发货的订单不能取消")
    ...

调用方只能捕获宽泛的 RuntimeError,很容易误伤其它运行时错误,也没法区分到底是“订单不存在”还是“订单状态不允许”。

✅ 使用自定义异常

class OrderCancelNotAllowedError(Exception):
    pass

def cancel_order(order_id):
    order = get_order(order_id)
    if order.status == "shipped":
        raise OrderCancelNotAllowedError(
            f"订单 {order_id} 已发货,无法取消"
        )

调用方可以精确捕获:

try:
    cancel_order("X123")
except OrderNotFoundError:
    print("订单号错了")
except OrderCancelNotAllowedError:
    print("这个订单不能取消,提示用户")

异常类名就是一份可读的文档,也把错误类型变成了一等公民。


🎨 自定义异常还能做什么?——携带更多上下文

自定义异常可以带任意属性,方便上层处理或记录日志:

class PaymentFailedError(Exception):
    def __init__(self, order_id: str, amount: float, reason: str):
        super().__init__(
            f"订单 {order_id} 支付 {amount} 元失败: {reason}"
        )
        self.order_id = order_id
        self.amount = amount
        self.reason = reason

# 使用
raise PaymentFailedError("X123", 99.9, "余额不足")

上层捕获时,可以直接用 e.order_id 拿到订单号做后续补偿逻辑,而不用去解析字符串。


📦 什么时候该用自定义异常?——一个判断法则

查看内嵌表格

典型层次结构长这样:

image.png

顶层捕获 AppError 可以兜底所有业务异常,与 Exception(系统级)区分开,防止误吞 KeyboardInterrupt 等。


🔧 进阶:异常链和 raise ... from

当你捕获一个底层异常并想转换为自定义异常时,记得保留“案发现场”:

class DataAccessError(Exception):
    pass

def load_config():
    try:
        with open("config.yaml") as f:
            ...
    except FileNotFoundError as e:
        raise DataAccessError("配置文件丢失") from e

from e 会设置 cause,打印堆栈时能看到完整的因果链,定位问题非常舒服。


🧪 最佳实践

  • ✅ 继承自 Exception,不要直接继承 BaseException(那是给系统级退出信号用的)。

  • ✅ 异常名以 Error 结尾,一目了然。

  • ✅ 把异常定义在靠近使用处,比如一个 exceptions.py 模块,集中管理。

  • ✅ 只在真正需要区分处理时才自定义,一两处就用内置异常也没问题。

  • ✅ 覆写 initstr,提供友好的错误消息。


✅ 总结

“自定义异常的价值不在于创造新语法,而在于用类名传递业务语义、用属性承载错误上下文,让上层代码能精确、优雅地处理不同的错误分支,把异常从‘报错消息’提升为‘领域语言’。”