业务异常必须继承Exception而非BaseException,以确保能被except Exception捕获;自定义异常应通过__init__接收code/message/details等参数,保留args[0]为message,并重写__str__添加错误码,避免异常类爆炸可采用工厂方法+错误码注册表统一管理。
为什么要继承
而不是
直接继承
会导致自定义异常无法被常规
捕获,因为
、
等系统级异常也继承自它。业务异常必须走
这条链,否则上线后可能“捕不到错却报错消失”,排查时一头雾水。
常见错误现象:
块完全不触发,日志里只看到未处理的
报错退出。
所有业务异常类都应显式继承
(或其子类如
)
避免用
这种无继承写法——Python 3 中默认不继承任何类,等价于
,不是异常类型
不要重写
时不调用
,否则
返回空字符串
如何在自定义异常中安全携带错误码和上下文数据
错误码不能只靠类名区分(比如
和
都是 404),得作为实例属性可读、可序列化。推荐用只读属性 + 初始化参数控制,避免运行时被意外修改。
使用场景:API 返回 JSON 时需透出
和
;日志采集需统一提取
字段。
立即学习
“
Python免费学习笔记(深入)
”;
用
类型,方便前端 switch 判断,也兼容 Prometheus 等监控系统打点
不要把
设为可变默认参数
—— 多次 raise 会共享同一字典对象
若需区分错误等级(如警告/错误/严重),可额外加
属性,但别混用 logging 级别名(如
),改用枚举或整数
和
怎么写才不影响调试和日志输出
默认
只返回 message,但运维查日志时往往需要一眼看到
。重写
加上错误码是合理做法;而
应保持“可重建对象”的风格,方便开发调试。
Python 3.14.3
微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。
下载
容易踩的坑:重写
后导致 Sentry 或其他 APM 工具提取 message 失败——它们通常依赖
,而非
。
始终保留
在
,即
不可省
可返回
,既清晰又不破坏原有行为
建议返回
,复制粘贴到 REPL 能直接执行
如何避免异常类爆炸——用工厂函数或注册表管理业务错误码
项目大了以后,几十个异常类散落在各模块,既难查找又易重复定义(比如两个模块都写了
)。与其让每个错误码对应一个类,不如用单个基类 + 类方法生成实例,再配合模块级错误码注册表。
性能影响极小,但能显著降低维护成本,尤其适合微服务间错误码对齐场景。
使用
raise BizError.new(ErrorCode.USER_NOT_FOUND, "用户不存在")
错误码常量统一收口在
类或
,禁止硬编码数字
不建议用
枚举类包装错误码——增加 import 依赖,且 IDE 对
成员的跳转支持不如普通属性
如果团队用 OpenAPI,错误码注释可直接从
类生成,避免文档和代码脱节
业务异常类本身不复杂,但一旦跨服务、跨语言、进日志系统、对接监控平台,错误码的类型、可读性、不可变性就会变成关键细节。最常被忽略的是
的一致性——它决定了 80% 的日志解析和告警规则是否生效。
ExceptionBaseExceptionBaseExceptionexcept Exception:SystemExitKeyboardInterruptExceptionexcept Exception:MyBusinessErrorExceptionValueErrorclass MyError:object__init__super().__init__(message)str(exc)UserNotFoundOrderNotFoundcodemessageerror_codeclass BizError(Exception):
def __init__(self, code: int, message: str, details: dict = None):
super().__init__(message)
self.code = code
self.details = details or {}
使用
raise BizError(code=1001, message="库存不足", details={"sku_id": "S123", "available": 0})
codeintdetails{}level"ERROR"__str____repr__str(exc)code__str____repr____str__exc.args[0]str(exc)messageargs[0]super().__init__(message)__str__f"[{self.code}] {self.args[0]}"__repr__f"{self.__class__.__name__}({self.code!r}, {self.args[0]!r})"UserLockedError(2003)# errors.py
class ErrorCode:
USER_NOT_FOUND = 1001
INSUFFICIENT_BALANCE = 1002
class BizError(Exception):
_registry = {}
def __init__(self, code: int, message: str, details: dict = None):
super().__init__(message)
self.code = code
self.details = details or {}
@classmethod
def new(cls, code: int, message: str, **kwargs):
return cls(code, message, kwargs)ErrorCodeerrors.py@uniqueEnumErrorCodeargs[0]