自定义异常
内置异常(ValueError、TypeError 等)描述的是 Python 语言层面的错误。但真实业务中有大量领域相关的错误——库存不足、订单已取消、用户权限不够——这些用内置异常表达既不直观,也不利于调用方精确捕获。自定义异常类让你用代码描述业务错误,让 API 的错误处理变得清晰、可扩展、可维护。
继承 Exception
自定义异常最简单的形式是继承 Exception:
class InventoryError(Exception):
"""库存相关错误的基类"""
pass
class OutOfStockError(InventoryError):
"""库存不足"""
pass
try:
raise OutOfStockError("商品 SKU-001 库存不足")
except InventoryError as e: # 父类捕获也能命中子类
print(f"捕获到库存错误:{e}")
# 输出:捕获到库存错误:商品 SKU-001 库存不足
:::tip 为什么继承 Exception 而不是 BaseException
继承 Exception(而不是 BaseException)的原因:
Exception是"普通错误"的基类,符合业务错误的语义except Exception能兜住你的自定义异常,但不会捕获KeyboardInterrupt、SystemExit等系统信号——这是你想要的行为- 继承
BaseException会让你的异常逃过except Exception兜底,造成意外崩溃 :::
异常命名约定
异常类的命名应当:
- 以
Error、Exception或Warning结尾(大多数情况下用Error) - 名字能反映问题本身,而不是触发场景(如
OutOfStockError而不是OrderFailedError) - 简洁明确,避免与内置异常冲突
异常类设计
基础模板
最简单的自定义异常只需一个类名和 docstring:
class ValidationError(Exception):
"""数据验证失败"""
pass
def validate_age(age: int) -> None:
if age < 0:
raise ValidationError("年龄不能为负数")
if age > 150:
raise ValidationError("年龄不能超过 150")
# 合法
调用父类构造器
如果想自定义异常接收参数,应当调用 super().__init__(*args) 保留标准行为(异常信息存入 args):
class InvalidEmailError(ValueError):
"""非法邮箱格式"""
def __init__(self, email: str, reason: str = "格式不正确"):
self.email = email # 保存触发错误的邮箱
self.reason = reason # 保存具体原因
super().__init__(f"非法邮箱 {email!r}:{reason}")
def validate_email(email: str) -> None:
if "@" not in email:
raise InvalidEmailError(email, "缺少 @ 符号")
if not email.endswith(".com") and not email.endswith(".cn"):
raise InvalidEmailError(email, "域名不支持")
try:
validate_email("alice")
except InvalidEmailError as e:
print(f"邮箱 {e.email} 无效:{e.reason}")
# 输出:邮箱 alice 无效:缺少 @ 符号
完整的异常类模板
一个完善的异常类通常包含:
class BusinessError(Exception):
"""业务错误基类。
所有业务异常应继承此类,便于统一捕获。
Attributes:
message: 错误描述信息。
code: 业务错误码,便于程序化处理。
"""
def __init__(self, message: str, code: str | None = None):
self.message = message
self.code = code
super().__init__(message)
def __str__(self) -> str:
if self.code:
return f"[{self.code}] {self.message}"
return self.message
class OrderNotFoundError(BusinessError):
"""订单不存在"""
def __init__(self, order_id: str):
self.order_id = order_id
super().__init__(
message=f"订单 {order_id} 不存在",
code="ORDER_NOT_FOUND",
)
try:
raise OrderNotFoundError("ORD-2025-001")
except BusinessError as e:
print(f"错误:{e}")
print(f"错误码:{e.code}")
if isinstance(e, OrderNotFoundError):
print(f"订单号:{e.order_id}")
# 输出:
# 错误:[ORDER_NOT_FOUND] 订单 ORD-2025-001 不存在
# 错误码:ORDER_NOT_FOUND
# 订单号:ORD-2025-001
添加额外属性
自定义异常的真正价值在于携带结构化的错误上下文。除了字符串信息,你可以把任意对象、错误码、字段名等附加到异常上,让调用方程序化地处理错误。
错误码
便于前端或 API 调用方按代码处理错误:
class AppError(Exception):
"""应用错误基类"""
code: str = "APP_ERROR" # 子类可覆盖默认错误码
def __init__(self, message: str = "", **context):
self.message = message or self.__class__.__doc__
self.context = context # 额外上下文
super().__init__(self.message)
def __str__(self) -> str:
prefix = f"[{self.code}]"
return f"{prefix} {self.message}" if self.message else prefix
class AuthenticationError(AppError):
"""用户认证失败"""
code = "AUTH_FAILED"
class PermissionDeniedError(AppError):
"""权限不足"""
code = "PERMISSION_DENIED"
class RateLimitError(AppError):
"""请求过于频繁"""
code = "RATE_LIMIT"
def __init__(self, retry_after: int, message: str = ""):
self.retry_after = retry_after # 建议重试间隔(秒)
super().__init__(message or f"请 {retry_after} 秒后再试")
# 调用方按错误码分流
def handle_api_error(exc: AppError):
if exc.code == "AUTH_FAILED":
print("跳转到登录页")
elif exc.code == "PERMISSION_DENIED":
print("显示权限不足提示")
elif exc.code == "RATE_LIMIT":
# RateLimitError 额外有 retry_after 属性
retry_after = exc.retry_after if isinstance(exc, RateLimitError) else 0
print(f"显示倒计时:{retry_after} 秒后重试")
else:
print(f"未知错误:{exc}")
for exc in [AuthenticationError(), PermissionDeniedError(), RateLimitError(30)]:
handle_api_error(exc)
# 输出:
# 跳转到登录页
# 显示权限不足提示
# 显示倒计时:30 秒后重试
字段级错误
表单验证时,错误需要知道是哪个字段出问题:
class FieldValidationError(ValueError):
"""字段验证错误"""
def __init__(self, field: str, value, reason: str):
self.field = field
self.value = value
self.reason = reason
super().__init__(f"字段 {field!r} 的值 {value!r} {reason}")
def validate_user_form(form: dict) -> None:
"""验证用户表单,遇错抛 FieldValidationError"""
if "username" not in form:
raise FieldValidationError("username", None, "是必填项")
if len(form["username"]) < 3:
raise FieldValidationError(
"username", form["username"], "长度至少 3 个字符"
)
if "@" not in form.get("email", ""):
raise FieldValidationError(
"email", form.get("email"), "格式不正确"
)
try:
validate_user_form({"username": "ab", "email": "bad-email"})
except FieldValidationError as e:
print(f"字段 {e.field!r} 验证失败:{e.reason}(当前值:{e.value!r})")
# 输出:字段 'username' 验证失败:长度至少 3 个字符(当前值:'ab')
异常层次设计
业务系统中往往有大量异常类。把它们组织成层次结构,既便于按"分组"捕获,也方便扩展新异常。
class AppError(Exception):
"""应用错误基类"""
class DatabaseError(AppError):
"""数据库相关"""
class ConnectionError(DatabaseError):
"""连接失败"""
class QueryError(DatabaseError):
"""查询失败"""
class ValidationError(AppError):
"""数据验证相关"""
class RequiredFieldError(ValidationError):
"""必填字段缺失"""
class InvalidFormatError(ValidationError):
"""格式不正确"""
class BusinessError(AppError):
"""业务规则相关"""
class InsufficientBalanceError(BusinessError):
"""余额不足"""
class OrderStatusError(BusinessError):
"""订单状态不允许操作"""
按层次捕获
def place_order(order):
try:
validate(order)
save_to_db(order)
charge_payment(order)
except ValidationError as e:
# 命中所有 ValidationError 子类(RequiredFieldError、InvalidFormatError)
return {"status": "validation_failed", "message": str(e)}
except DatabaseError as e:
# 命中所有数据库相关错误
log_error(e)
return {"status": "service_unavailable"}
except BusinessError as e:
# 命中所有业务规则错误
return {"status": "business_error", "message": str(e)}
except AppError as e:
# 兜底所有应用错误
return {"status": "error", "message": str(e)}
:::tip 层次设计原则
- 顶层:定义一个统一的业务异常基类(如
AppError),便于"捕获所有业务异常" - 中层:按子系统或错误大类分组(
DatabaseError、ValidationError、BusinessError) - 叶子:具体的错误类型(
RequiredFieldError、InsufficientBalanceError) - 不要过早细分——只在确实需要区分处理时才添加子类 :::
异常链 cause
raise X from Y 把 Y 存到 X.__cause__ 上,形成"因果链"。这在底层错误向业务错误转换时非常有用。
class UserServiceError(Exception):
"""用户服务错误"""
class UserNotFoundError(UserServiceError):
"""用户不存在"""
def get_user(user_id: int):
"""从数据库获取用户,把底层错误转成业务错误"""
try:
# 模拟数据库查询,可能抛 KeyError
users = {1: {"name": "Alice"}, 2: {"name": "Bob"}}
return users[user_id]
except KeyError as exc:
# 转换为业务异常,保留原始异常作为原因
raise UserNotFoundError(f"用户 ID={user_id} 不存在") from exc
try:
user = get_user(999)
except UserNotFoundError as e:
print(f"业务异常:{e}")
print(f"原始原因:{e.__cause__}")
print(f"原因类型:{type(e.__cause__).__name__}")
# 输出:
# 业务异常:用户 ID=999 不存在
# 原始原因:999
# 原因类型:KeyError
异常链的传递
异常链会沿调用栈传递,便于调试时还原完整因果:
class LowLevelError(Exception):
pass
class MidLevelError(Exception):
pass
class HighLevelError(Exception):
pass
def low_level():
raise LowLevelError("网络连接失败")
def mid_level():
try:
low_level()
except LowLevelError as e:
raise MidLevelError("数据加载失败") from e
def high_level():
try:
mid_level()
except MidLevelError as e:
raise HighLevelError("用户操作无法完成") from e
try:
high_level()
except HighLevelError as e:
# 沿链溯源
cur = e
depth = 0
while cur is not None:
print(f" {' ' * depth}{type(cur).__name__}: {cur}")
cur = cur.__cause__
depth += 1
# 输出:
# HighLevelError: 用户操作无法完成
# MidLevelError: 数据加载失败
# LowLevelError: 网络连接失败
:::info cause vs context
__cause__:通过raise X from Y显式设置,表示"Y 是 X 的原因"__context__:在except块内隐式抛出新异常时自动设置,表示"在处理 X 时又发生了 Y"
两者都可以溯源,但语义不同。有意识地转换异常时用 from(__cause__),让因果更明确。
:::
ExceptionGroup 异常分组
Python 3.11+ 的 ExceptionGroup 不仅能用于并发场景,在批量验证中也十分有用——可以一次性收集所有错误,而不是遇到第一个就停下。
批量验证示例
class FormValidationError(ExceptionGroup):
"""表单验证错误组(包含多个字段错误)"""
def __init__(self, message: str, errors: list[Exception]):
super().__init__(message, errors)
class FieldError(Exception):
"""单个字段错误"""
def __init__(self, field: str, reason: str):
self.field = field
self.reason = reason
super().__init__(f"{field}: {reason}")
def validate_register_form(form: dict) -> None:
"""注册表单验证:收集所有错误一次性抛出"""
errors: list[Exception] = []
# 用户名校验
username = form.get("username", "")
if not username:
errors.append(FieldError("username", "必填"))
elif len(username) < 3:
errors.append(FieldError("username", "长度至少 3 个字符"))
# 密码校验
password = form.get("password", "")
if not password:
errors.append(FieldError("password", "必填"))
elif len(password) < 8:
errors.append(FieldError("password", "长度至少 8 个字符"))
if password and not any(c.isdigit() for c in password):
errors.append(FieldError("password", "必须包含数字"))
# 邮箱校验
email = form.get("email", "")
if not email:
errors.append(FieldError("email", "必填"))
elif "@" not in email:
errors.append(FieldError("email", "格式不正确"))
# 一次性抛出所有错误
if errors:
raise FormValidationError("表单验证失败", errors)
# 用 except* 按类型处理
def handle_registration(form: dict) -> bool:
try:
validate_register_form(form)
except* FieldError as eg:
# eg.exceptions 是所有 FieldError 列表
print(f"发现 {len(eg.exceptions)} 个字段错误:")
for exc in eg.exceptions:
print(f" - {exc.field}: {exc.reason}")
return False
except* Exception as eg:
print(f"未预期错误:{eg.exceptions}")
return False
else:
print("注册成功!")
return True
# 测试:一次显示所有错误
handle_registration({
"username": "ab",
"password": "weak",
"email": "bad-email",
})
# 输出:
# 发现 4 个字段错误:
# - username: 长度至少 3 个字符
# - password: 长度至少 8 个字符
# - password: 必须包含数字
# - email: 格式不正确
:::tip 对比:抛一个 vs 抛一组
传统做法遇到第一个错误就 raise,用户每次只能看到一个错误,需要反复提交。用 ExceptionGroup 一次性收集所有错误,用户体验显著提升——尤其在表单验证、批量数据处理场景中。
如果你只关心单个错误,传统 raise ValueError(...) 仍然是最简单的方案。ExceptionGroup 适合"需要一次看全部"的场景。
:::
最佳实践
1. 异常类一定要继承 Exception(或更具体的子类)
# ❌ 反例:继承 BaseException
class MyError(BaseException):
pass
# 这样写后,except Exception 兜不住它,容易造成意外崩溃
# ✅ 正例:继承 Exception 或更具体的内置异常
class MyError(Exception):
pass
class MyValueError(ValueError): # 如果语义上是"值错误"
pass
2. 总是提供清晰的错误信息
# ❌ 反例:无信息
raise ValidationError()
# ❌ 反例:模糊
raise ValidationError("错误")
# ✅ 正例:信息具体、可操作
raise ValidationError(f"字段 'age' 的值 {age} 不在 0-150 范围内")
3. 自定义 str 但保留 args
class OrderError(Exception):
def __init__(self, order_id: str, reason: str):
self.order_id = order_id
self.reason = reason
# 调用 super().__init__ 让 args 保留原始信息
super().__init__(order_id, reason)
def __str__(self) -> str:
return f"订单 {self.order_id}:{self.reason}"
e = OrderError("ORD-001", "已取消")
print(str(e)) # 输出:订单 ORD-001:已取消
print(e.args) # 输出:('ORD-001', '已取消')
print(e.order_id) # 输出:ORD-001
4. 不要用异常做流程控制
# ❌ 反例:用异常做循环退出
def find_first_even(numbers):
try:
for n in numbers:
if n % 2 == 0:
raise StopIteration(n) # 滥用异常!
except StopIteration as e:
return e.value
# ✅ 正例:用普通 return
def find_first_even(numbers):
for n in numbers:
if n % 2 == 0:
return n
return None
异常应表示真正的异常情况,而不是普通的控制流分支。滥用异常会让代码可读性下降、性能下降(异常处理比条件判断慢得多)。
5. 在合适的层次处理异常
# ❌ 反例:底层捕获并吞掉,上层无法感知
def save(data):
try:
db.insert(data)
except DatabaseError:
pass # 静默吞掉,bug 不会被及时发现
# ✅ 正例:底层转换异常,上层决定处理方式
def save(data):
try:
db.insert(data)
except DatabaseError as e:
log.error("保存失败", exc_info=True)
raise SaveError("保存失败,请重试") from e
6. 优先用 except 的"具体类型"而非 isinstance
class AppError(Exception):
pass
class NotFoundError(AppError):
pass
# ✅ 用 except 区分类型
try:
do_work()
except NotFoundError:
handle_not_found()
except AppError:
handle_other()
# ❌ 不要用 isinstance(更啰嗦,且无法捕获异常)
try:
do_work()
except AppError as e:
if isinstance(e, NotFoundError):
handle_not_found()
else:
handle_other()
7. 用 ExceptionGroup 替代"循环收集 + 末尾抛出"
如果你的代码有这样的模式:
# 旧模式:手动收集再抛
errors = []
for item in items:
try:
process(item)
except SomeError as e:
errors.append(e)
if errors:
raise AggregatedError(errors)
可以考虑用 Python 3.11+ 的 ExceptionGroup 直接表达:
# 新模式:用 ExceptionGroup
errors = []
for item in items:
try:
process(item)
except SomeError as e:
errors.append(e)
if errors:
raise ExceptionGroup("批量处理失败", errors)
# 调用方用 except* 按类型处理
try:
batch_process(items)
except* SomeError as eg:
for exc in eg.exceptions:
handle_error(exc)
实战:库存系统异常体系
下面构建一个简化的库存管理系统的异常体系,演示完整的自定义异常设计:
from __future__ import annotations
# ========== 异常体系定义 ==========
class InventoryError(Exception):
"""库存系统所有错误的基类。
所有库存相关异常都继承自此,便于统一捕获和日志记录。
Attributes:
sku: 受影响的商品 SKU 编码。
"""
def __init__(self, message: str, sku: str | None = None):
self.sku = sku
self.message = message
super().__init__(self._format())
def _format(self) -> str:
prefix = f"[SKU={self.sku}] " if self.sku else ""
return f"{prefix}{self.message}"
class StockError(InventoryError):
"""库存数量相关错误"""
class InsufficientStockError(StockError):
"""库存不足"""
def __init__(self, sku: str, requested: int, available: int):
self.requested = requested
self.available = available
super().__init__(
message=f"库存不足:需要 {requested},当前 {available}",
sku=sku,
)
class InvalidStockError(StockError):
"""非法库存操作"""
def __init__(self, sku: str, reason: str):
super().__init__(message=reason, sku=sku)
class ProductError(InventoryError):
"""商品信息相关错误"""
class ProductNotFoundError(ProductError):
"""商品不存在"""
def __init__(self, sku: str):
super().__init__(message=f"商品 {sku} 不存在", sku=sku)
class DuplicateProductError(ProductError):
"""商品 SKU 重复"""
def __init__(self, sku: str):
super().__init__(message=f"商品 SKU {sku} 已存在", sku=sku)
# ========== 业务逻辑实现 ==========
class Product:
def __init__(self, sku: str, name: str, stock: int):
self.sku = sku
self.name = name
self._stock = stock
@property
def stock(self) -> int:
return self._stock
def __repr__(self) -> str:
return f"Product(sku={self.sku!r}, name={self.name!r}, stock={self._stock})"
class Inventory:
"""库存管理"""
def __init__(self):
self._products: dict[str, Product] = {}
def add_product(self, sku: str, name: str, stock: int) -> Product:
"""添加新商品"""
if sku in self._products:
raise DuplicateProductError(sku)
if stock < 0:
raise InvalidStockError(sku, "初始库存不能为负数")
product = Product(sku, name, stock)
self._products[sku] = product
return product
def get_product(self, sku: str) -> Product:
"""查询商品"""
if sku not in self._products:
raise ProductNotFoundError(sku)
return self._products[sku]
def decrease_stock(self, sku: str, quantity: int) -> int:
"""扣减库存"""
product = self.get_product(sku) # 可能抛 ProductNotFoundError
if quantity <= 0:
raise InvalidStockError(sku, "扣减数量必须为正数")
if quantity > product.stock:
raise InsufficientStockError(sku, quantity, product.stock)
product._stock -= quantity
return product.stock
def increase_stock(self, sku: str, quantity: int) -> int:
"""增加库存"""
product = self.get_product(sku)
if quantity <= 0:
raise InvalidStockError(sku, "增加数量必须为正数")
product._stock += quantity
return product.stock
# ========== 演示使用 ==========
def demo_inventory():
inv = Inventory()
# 添加商品
inv.add_product("SKU-001", "Python 入门书", 10)
inv.add_product("SKU-002", "机械键盘", 5)
print("初始商品:", list(inv._products.values()))
# 1. 商品不存在
print("\n--- 查询不存在的商品 ---")
try:
inv.get_product("SKU-999")
except ProductNotFoundError as e:
print(f"捕获:{e}")
# 2. 重复添加商品
print("\n--- 重复添加商品 ---")
try:
inv.add_product("SKU-001", "另一本书", 3)
except DuplicateProductError as e:
print(f"捕获:{e}")
# 3. 库存不足
print("\n--- 库存不足 ---")
try:
inv.decrease_stock("SKU-002", 100)
except InsufficientStockError as e:
print(f"捕获:{e}")
print(f" 需求量:{e.requested},当前库存:{e.available}")
# 4. 按层次捕获:父类捕获也能命中子类
print("\n--- 父类统一捕获 ---")
try:
inv.decrease_stock("SKU-001", -5)
except InventoryError as e: # 兜住所有库存异常
print(f"捕获:{e}")
# 5. 正常操作
print("\n--- 正常操作 ---")
remaining = inv.decrease_stock("SKU-001", 3)
print(f"扣减后 SKU-001 剩余:{remaining}")
remaining = inv.increase_stock("SKU-002", 10)
print(f"增加后 SKU-002 库存:{remaining}")
# ========== 批量订单处理(使用 ExceptionGroup)==========
def process_order_batch(
inv: Inventory, orders: list[tuple[str, int]]
) -> dict[str, int | str]:
"""批量处理订单,每个 SKU 独立扣减,失败的留下错误信息"""
results: dict[str, int | str] = {}
errors: list[Exception] = []
for sku, qty in orders:
try:
remaining = inv.decrease_stock(sku, qty)
results[sku] = remaining
except InventoryError as e:
results[sku] = f"失败:{e}"
errors.append(e)
# 如果有错误,把所有错误打包成 ExceptionGroup 抛出
# 同时仍返回部分成功的结果(通过 results 字典)
if errors:
# 这里演示同时返回结果和抛出异常的混合模式
print(f"批量处理完成,{len(errors)} 个失败:")
for sku, result in results.items():
print(f" {sku}: {result}")
raise ExceptionGroup(f"{len(errors)} 个订单处理失败", errors)
return results
def demo_batch():
inv = Inventory()
inv.add_product("SKU-A", "商品 A", 50)
inv.add_product("SKU-B", "商品 B", 5)
inv.add_product("SKU-C", "商品 C", 20)
orders = [
("SKU-A", 10), # 成功
("SKU-B", 100), # 库存不足
("SKU-D", 5), # 商品不存在
("SKU-C", 20), # 成功
("SKU-A", -3), # 非法数量
]
print("\n--- 批量订单处理 ---")
try:
results = process_order_batch(inv, orders)
print("全部成功:", results)
except* InsufficientStockError as eg:
for exc in eg.exceptions:
print(f" 库存不足:{exc}")
except* ProductNotFoundError as eg:
for exc in eg.exceptions:
print(f" 商品不存在:{exc}")
except* InvalidStockError as eg:
for exc in eg.exceptions:
print(f" 非法操作:{exc}")
except* InventoryError as eg:
# 兜底其他库存异常
for exc in eg.exceptions:
print(f" 其他库存错误:{exc}")
demo_inventory()
demo_batch()
输出示例:
初始商品: [Product(sku='SKU-001', name='Python 入门书', stock=10), Product(sku='SKU-002', name='机械键盘', stock=5)]
--- 查询不存在的商品 ---
捕获:[SKU=SKU-999] 商品 SKU-999 不存在
--- 重复添加商品 ---
捕获:[SKU=SKU-001] 商品 SKU SKU-001 已存在
--- 库存不足 ---
捕获:[SKU=SKU-002] 库存不足:需要 100,当前 5
需求量:100,当前库存:5
--- 父类统一捕获 ---
捕获:[SKU=SKU-001] 扣减数量必须为正数
--- 正常操作 ---
扣减后 SKU-001 剩余:7
增加后 SKU-002 库存:15
--- 批量订单处理 ---
批量处理完成,3 个失败:
SKU-A: 失败:[SKU=SKU-A] 扣减数量必须为正数
SKU-B: 失败:[SKU=SKU-B] 库存不足:需要 100,当前 15
SKU-D: 失败:[SKU=SKU-D] 商品 SKU-D 不存在
SKU-C: 0
其他库存错误:[SKU=SKU-A] 扣减数量必须为正数
库存不足:[SKU=SKU-B] 库存不足:需要 100,当前 15
商品不存在:[SKU=SKU-D] 商品 SKU-D 不存在
:::tip 注意批量处理的细节
上面 process_order_batch 的实现有个微妙之处:SKU-A 第一次扣减 10 成功了,第二次扣减 -3 失败。所以 SKU-A 在 results 中显示的是失败信息(最后一次的结果覆盖了成功值)。在生产代码中,应当区分"成功记录"和"失败记录"两个集合,避免这种歧义。
ExceptionGroup 让我们能在一次调用中报告所有失败,而 except* 让上层按异常类型分别处理——这是异常处理从"单错误"到"多错误"的重要演进。
:::
小结
- 自定义异常继承
Exception(或更具体的内置异常),不要继承BaseException - 异常类命名以
Error/Exception结尾,名字反映问题本身 - 调用
super().__init__(*args)保留标准行为,自定义__str__改善展示 - 通过额外属性携带结构化上下文(错误码、字段名、原始值等),便于程序化处理
- 设计异常层次结构:顶层基类 → 中层分组 → 叶子具体异常,便于按层次捕获
- 用
raise X from Y建立__cause__链,保留底层异常上下文 - Python 3.11+ 的
ExceptionGroup适合"批量收集错误一次抛出",配合except*分别处理 - 最佳实践:清晰的错误信息、不在底层静默吞异常、不用异常做流程控制
- 实战:库存系统展示完整的异常体系设计,含层次、属性、异常组批量处理
下一节将学习上下文管理器——一种把"资源获取"和"资源释放"自动配对的优雅机制,让异常情况下的资源清理变得可靠。