自定义异常应继承exception而非baseexception,以确保被常规except exception捕获;层级按错误语义域与处理策略划分,避免冗余;需合理使用raise...from和添加error_code等必要属性,并重写__str__。

为什么自定义异常要继承 Exception 而不是 BaseException
直接继承 BaseException 会导致异常被 except Exception: 漏掉,因为绝大多数 try/except 块只捕获 Exception 及其子类,而 SystemExit、KeyboardInterrupt 这些是 BaseException 的直系子类,设计上就该绕过常规错误处理流程。
你写业务逻辑时,99% 的场景下希望自定义异常能被标准错误兜底捕获,所以必须从 Exception 开始派生。
- 别用
class MyError(BaseException):—— 它会逃逸出常规except Exception: - 推荐写法:
class ValidationError(Exception):或更细粒度地class FieldValidationError(ValidationError): - 如果真需要中断程序(比如主动退出),应该用
sys.exit(),而不是抛一个BaseException子类
怎么组织多级异常类让调用方好判断又不冗余
层级不是越深越好,关键在「是否影响错误处理策略」。比如 HTTP API 返回 400 和 500,前端重试逻辑不同;但「邮箱格式错」和「手机号格式错」都该走同一类校验失败路径,没必要拆成两个顶层异常。
建议按「错误语义域 + 处理方式」分层,而非按字段或模块硬切:
西安网上购物网店系统的主要亮点:(1)商品的分类更加细化和明朗,可以三级分类,价格可以多层次\多级别,按照后台设置的,吸引会员加入。(2)会员和非会员购物并存,订单直接支付和会员帐户支付并存,电话支付与网上支付多种支付方式。(3)自定义商品扩展属性,多种扩展属性定义模式,强大的商品管理功能,多重分类功能(4)灵活的会员积分系统,灵活的会员权限控制,模版丰富多彩,模版代码分离,方便修改模版(5)支付
立即学习“Python免费学习笔记(深入)”;
- 顶层:如
BusinessError(所有业务逻辑可预期错误) - 二级:如
InputError(客户端可改)、ExternalServiceError(需降级或重试) - 三级:仅当处理逻辑确实不同才加,例如
RateLimitExceeded(ExternalServiceError)需要 sleep 后重试,而ServiceUnavailable(ExternalServiceError)应直接降级 - 避免出现
UserNotFoundError和OrderNotFoundError并列在同级——它们都属于ResourceNotFoundError,ID 无效就是 ID 无效,不因资源类型而异
raise 时要不要带原始异常链(raise ... from)
要,但得看场景。底层库抛了 requests.exceptions.ConnectionError,你封装成 PaymentGatewayError 时,用 raise PaymentGatewayError("支付网关连接失败") from e 能保留原始 traceback,方便排查网络问题;但如果只是做参数校验,if not email: raise ValidationError("email 不能为空") 就足够,没必要 from。
- 涉及外部依赖(HTTP、DB、文件系统)且可能需要诊断底层原因 → 用
raise NewError(...) from original_exc - 纯业务规则检查(如长度、枚举值、状态流转)→ 直接 raise,干净利落
- 注意:用了
from后,打印 traceback 会显示两段堆栈,第一段是你新异常,第二段是原始异常,别误以为是重复报错
自定义异常类里该不该加额外属性(比如 error_code、details)
该加,但别塞太多。API 错误码、前端可展示的字段名、需要结构化返回的信息,这些值得作为实例属性暴露;但像「触发时间」「当前用户 ID」这类上下文信息,更适合放在日志里打出来,而不是塞进异常对象。
- 推荐加:
self.error_code: str(用于前端 switch 分支)、self.field: Optional[str](标出哪个字段错)、self.details: dict(供调试的补充信息) - 不推荐加:
self.request_id、self.timestamp、self.user—— 这些不属于错误本质,混进去会让异常类职责膨胀 - 记得重写
__str__,让print(e)有可读性,比如返回f"[{self.error_code}] {self.args[0]}"
异常层级本身不难设计,难的是每次抛出前想清楚:这个错误,下游是想重试、提示用户、还是记录后忽略?答案不同,层级和属性就该不同。很多人卡在“先写个基类再说”,结果后面全靠 if/elif 判断 type(e),那就白建了。








