内置异常的层次
记住几个常见的:
BaseException
├── SystemExit
├── KeyboardInterrupt
└── Exception ← 业务代码 catch 的根
├── ValueError
│ └── UnicodeError
├── TypeError
├── KeyError
├── IndexError
├── FileNotFoundError
├── PermissionError
├── ZeroDivisionError
├── RuntimeError
│ ├── RecursionError
│ └── NotImplementedError
└── StopIteration
永远 except Exception,不要 except BaseException——后者会吞掉 Ctrl+C。
何时该 raise
- 参数不合法(值 / 类型):
ValueError/TypeError - 状态错:
RuntimeError - 业务规则违反:自定义异常
- 必须在子类实现:
NotImplementedError
def transfer(from_acc, to_acc, amount):
if amount <= 0:
raise ValueError("金额必须为正")
if amount > from_acc.balance:
raise InsufficientFunds(f"账户 {from_acc.id} 余额不足")
...
何时该 catch
只 catch 你能处理的——其他放任抛出,让上层决定:
# 好:能处理(给默认值)
def to_int(s):
try:
return int(s)
except ValueError:
return 0
# 坏:catch 但啥也不做
try:
do_something()
except Exception:
pass # 隐藏 bug
自定义异常类
继承 Exception 即可:
class AppError(Exception):
"""所有业务异常的基类"""
class NotFound(AppError):
"""找不到资源"""
class PermissionDenied(AppError):
"""没权限"""
class InsufficientFunds(AppError):
def __init__(self, msg, current=0, required=0):
super().__init__(msg)
self.current = current
self.required = required
try:
transfer(...)
except InsufficientFunds as e:
print(f"需要 {e.required},账户只有 {e.current}")
except AppError as e: # 兜底所有业务异常
print(f"业务错误: {e}")
异常链:from
转换异常时,保留原始 traceback:
def load_config(path):
try:
with open(path) as f:
return json.loads(f.read())
except FileNotFoundError as e:
raise ConfigError("配置不存在") from e
except json.JSONDecodeError as e:
raise ConfigError("配置格式错") from e
from e 让 traceback 显示"this is the cause of",便于排查。
raise from None:故意隐藏内部细节
try:
parse_internal()
except InternalError:
raise UserFacingError("配置错误") from None # 不暴露内部异常
异常应该是异常
不要用异常做"正常流程控制":
# 反例:用异常控制
def find(x):
try:
return DB[x]
except KeyError:
return None
# 更地道
def find(x):
return DB.get(x)
异常的代价比 if 高很多——用在真正"不期望发生"的事情上。
上下文管理器抑制异常
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove("maybe.txt") # 不存在就算了
比写 try: ... except: pass 更优雅。
实战:API 错误处理框架
class APIError(Exception):
status_code = 500
message = "服务器错误"
class NotFoundError(APIError):
status_code = 404
message = "资源不存在"
class ValidationError(APIError):
status_code = 400
message = "请求参数错误"
@app.errorhandler(APIError)
def handle(e):
return {"error": e.message}, e.status_code
整个项目用一套异常体系,错误处理一处搞定。
下一篇讲日志 logging——为什么 print 不够。