跳到主要内容

我们必须知道,我们终将知道。

David Hilbert数学公理化之父

🛡️ 自定义异常

内置异常(ValueErrorTypeError 等)描述的是 Python 语言层面的错误。但真实业务中有大量领域相关的错误——传感器故障、执行器失效、通信中断——这些用内置异常表达既不直观,也不利于调用方精确捕获。自定义异常类可以用代码描述业务错误,让 API 的错误处理变得清晰、可扩展、可维护。

📌 本节要点

  • 自定义异常继承 Exception(或更具体的内置异常),不要继承 BaseException
  • 异常类命名以 Error/Exception 结尾,名字反映问题本身
  • 调用 super().__init__(*args) 保留标准行为,自定义 __str__ 改善展示
  • 通过额外属性携带结构化上下文(错误码、组件ID、原始值等),便于程序化处理
  • 设计异常层次结构:顶层基类 → 中层分组 → 叶子具体异常,便于按层次捕获
  • raise X from Y 建立 __cause__ 链,保留底层异常上下文
  • Python 3.11+ 的 ExceptionGroup 适合"批量收集错误一次抛出",配合 except* 分别处理
  • 最佳实践:清晰的错误信息、不在底层静默吞异常、不用异常做流程控制 :::
自定义异常快速体验

继承 Exception

自定义异常最简单的形式是继承 Exception

Python
class FlightSystemError(Exception):
"""飞行系统相关错误的基类"""
pass


class SensorError(FlightSystemError):
"""传感器错误"""
pass


try:
raise SensorError("IMU-001 传感器读数异常")
except FlightSystemError as e: # 父类捕获也能命中子类
print(f"捕获到飞行系统错误:{e}")
# 输出:捕获到飞行系统错误:IMU-001 传感器读数异常
为什么继承 Exception 而不是 BaseException

继承 Exception(而不是 BaseException)的原因:

  • Exception 是"普通错误"的基类,符合业务错误的语义
  • except Exception 能兜住自定义异常,但不会捕获 KeyboardInterruptSystemExit 等系统信号——这正是期望的行为
  • 继承 BaseException 会让异常逃过 except Exception 兜底,造成意外崩溃

异常命名约定

异常类的命名应当:

  • ErrorExceptionWarning 结尾(大多数情况下用 Error
  • 名字能反映问题本身,而不是触发场景(如 SensorError 而不是 IMUFailedError
  • 简洁明确,避免与内置异常冲突

异常类设计

基础模板

最简单的自定义异常只需一个类名和 docstring:

Python
class ValidationError(Exception):
"""数据验证失败"""
pass


def validate_sensor_reading(reading: float) -> None:
if reading < -180 or reading > 180:
raise ValidationError("传感器读数超出范围")
# 合法

调用父类构造器

如果想自定义异常接收参数,应当调用 super().__init__(*args) 保留标准行为(异常信息存入 args):

Python
class InvalidSensorError(ValueError):
"""非法传感器数据"""

def __init__(self, sensor_id: str, value: float, reason: str = "数据格式错误"):
self.sensor_id = sensor_id # 保存触发错误的传感器
self.value = value # 保存原始值
self.reason = reason # 保存具体原因
super().__init__(f"传感器 {sensor_id}{value} 无效:{reason}")


def validate_imu_data(imu_id: str, value: float) -> None:
if abs(value) > 1000:
raise InvalidSensorError(imu_id, value, "加速度值异常")
if value != value: # NaN check
raise InvalidSensorError(imu_id, value, "数据为 NaN")


try:
validate_imu_data("IMU-001", 1500.0)
except InvalidSensorError as e:
print(f"传感器 {e.sensor_id} 无效:{e.reason}(当前值:{e.value})")
# 输出:传感器 IMU-001 无效:加速度值异常(当前值:1500.0)

完整的异常类模板

一个完善的异常类通常包含:

Python
class NavigationError(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 WaypointNotFoundError(NavigationError):
"""航点不存在"""

def __init__(self, waypoint_id: str):
self.waypoint_id = waypoint_id
super().__init__(
message=f"航点 {waypoint_id} 不存在",
code="WAYPOINT_NOT_FOUND",
)


try:
raise WaypointNotFoundError("WP-2025-001")
except NavigationError as e:
print(f"错误:{e}")
print(f"错误码:{e.code}")
if isinstance(e, WaypointNotFoundError):
print(f"航点号:{e.waypoint_id}")
# 输出:
# 错误:[WAYPOINT_NOT_FOUND] 航点 WP-2025-001 不存在
# 错误码:WAYPOINT_NOT_FOUND
# 航点号:WP-2025-001

添加额外属性

自定义异常的真正价值在于携带结构化的错误上下文。除了字符串信息,我们可以把任意对象、错误码、组件ID等附加到异常上,让调用方程序化地处理错误。

错误码

便于前端或 API 调用方按代码处理错误:

Python
class FlightComponentError(Exception):
"""飞行组件错误基类"""

code: str = "COMPONENT_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 CalibrationError(FlightComponentError):
"""校准失败"""
code = "CALIBRATION_FAILED"


class CommunicationError(FlightComponentError):
"""通信中断"""
code = "COMM_LOST"


class TimeoutError(FlightComponentError):
"""响应超时"""
code = "TIMEOUT"

def __init__(self, timeout_ms: int, message: str = ""):
self.timeout_ms = timeout_ms # 超时时间(毫秒)
super().__init__(message or f"操作超时:{timeout_ms}ms")


# 调用方按错误码分流
def handle_component_error(exc: FlightComponentError):
if exc.code == "CALIBRATION_FAILED":
print("重新校准组件")
elif exc.code == "COMM_LOST":
print("检查通信链路")
elif exc.code == "TIMEOUT":
# TimeoutError 额外有 timeout_ms 属性
timeout_ms = exc.timeout_ms if isinstance(exc, TimeoutError) else 0
print(f"显示重试提示:{timeout_ms}ms 后重试")
else:
print(f"未知错误:{exc}")


for exc in [CalibrationError(), CommunicationError(), TimeoutError(5000)]:
handle_component_error(exc)
# 输出:
# 重新校准组件
# 检查通信链路
# 显示重试提示:5000ms 后重试

组件级错误

飞行系统中,错误需要知道是哪个组件出问题:

Python
class ComponentError(ValueError):
"""组件错误"""

def __init__(self, component_id: str, component_type: str, reason: str):
self.component_id = component_id
self.component_type = component_type
self.reason = reason
super().__init__(f"组件 {component_id}{component_type}{reason}")


def validate_flight_component(component_id: str, component_type: str, data: dict) -> None:
"""验证飞行组件数据,遇错抛 ComponentError"""
if "serial" not in data:
raise ComponentError(component_id, component_type, "序列号缺失")
if "firmware_version" not in data:
raise ComponentError(component_id, component_type, "固件版本未知")
if data.get("status") == "fault":
raise ComponentError(component_id, component_type, "处于故障状态")


try:
validate_flight_component("ESC-001", "电调", {"serial": "SN123"})
except ComponentError as e:
print(f"组件 {e.component_id}{e.component_type})验证失败:{e.reason}")
# 输出:组件 ESC-001(电调)验证失败:固件版本未知

异常层次设计

飞行系统中往往有大量异常类。把它们组织成层次结构,既便于按"分组"捕获,也方便扩展新异常。

Python
class FlightSystemError(Exception):
"""飞行系统错误基类"""

class SensorError(FlightSystemError):
"""传感器相关"""

class IMUError(SensorError):
"""惯性测量单元错误"""

class GPSError(SensorError):
"""GPS 错误"""

class ActuatorError(FlightSystemError):
"""执行器相关"""

class MotorError(ActuatorError):
"""电机错误"""

class ServoError(ActuatorError):
"""舵机错误"""

class ControllerError(FlightSystemError):
"""控制器相关"""

class NavigationError(FlightSystemError):
"""导航相关"""

class PathPlanningError(NavigationError):
"""路径规划错误"""

class CommunicationError(FlightSystemError):
"""通信相关"""

按层次捕获

Python
def process_flight_command(command):
try:
validate_command(command)
execute_actuator(command)
update_navigation(command)
except SensorError as e:
# 命中所有传感器错误(IMUError、GPSError)
return {"status": "sensor_fault", "message": str(e)}
except ActuatorError as e:
# 命中所有执行器错误
log_error(e)
return {"status": "actuator_fault"}
except NavigationError as e:
# 命中所有导航错误
return {"status": "navigation_error", "message": str(e)}
except FlightSystemError as e:
# 兜底所有飞行系统错误
return {"status": "system_error", "message": str(e)}
层次设计原则
  • 顶层:定义一个统一的业务异常基类(如 FlightSystemError),便于"捕获所有业务异常"
  • 中层:按子系统或错误大类分组(SensorErrorActuatorErrorNavigationError
  • 叶子:具体的错误类型(IMUErrorMotorError
  • 不要过早细分——只在确实需要区分处理时才添加子类

异常链 cause

raise X from YY 存到 X.__cause__ 上,形成"因果链"。这在底层错误向业务错误转换时非常有用。

Python
class FlightServiceError(Exception):
"""飞行服务错误"""

class ComponentNotFoundError(FlightServiceError):
"""组件不存在"""


def get_flight_component(component_id: str):
"""从系统获取组件,把底层错误转成业务错误"""
try:
# 模拟组件查询,可能抛 KeyError
components = {"IMU-001": {"type": "imu", "status": "ok"}, "GPS-001": {"type": "gps", "status": "ok"}}
return components[component_id]
except KeyError as exc:
# 转换为业务异常,保留原始异常作为原因
raise ComponentNotFoundError(f"组件 {component_id} 不存在") from exc


try:
component = get_flight_component("ESC-999")
except ComponentNotFoundError as e:
print(f"业务异常:{e}")
print(f"原始原因:{e.__cause__}")
print(f"原因类型:{type(e.__cause__).__name__}")
# 输出:
# 业务异常:组件 ESC-999 不存在
# 原始原因:'ESC-999'
# 原因类型:KeyError

异常链的传递

异常链会沿调用栈传递,便于调试时还原完整因果:

Python
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: 硬件通信失败
cause vs context
  • __cause__:通过 raise X from Y 显式设置,表示"Y 是 X 的原因"
  • __context__:在 except 块内隐式抛出新异常时自动设置,表示"在处理 X 时又发生了 Y"

两者都可以溯源,但语义不同。有意识地转换异常时用 from__cause__),让因果更明确。

ExceptionGroup 异常分组

Python 3.11+ 的 ExceptionGroup 不仅能用于并发场景,在批量验证中也十分有用——可以一次性收集所有错误,而不是遇到第一个就停下。

批量验证示例

Python
class SystemCheckError(ExceptionGroup):
"""系统检查错误组(包含多个组件错误)"""

def __init__(self, message: str, errors: list[Exception]):
super().__init__(message, errors)


class ComponentCheckError(Exception):
"""单个组件检查错误"""

def __init__(self, component_id: str, reason: str):
self.component_id = component_id
self.reason = reason
super().__init__(f"{component_id}: {reason}")


def validate_flight_system(components: dict) -> None:
"""飞行系统检查:收集所有错误一次性抛出"""
errors: list[Exception] = []

# IMU 检查
imu = components.get("imu", {})
if not imu.get("serial"):
errors.append(ComponentCheckError("IMU", "序列号缺失"))
if imu.get("status") == "fault":
errors.append(ComponentCheckError("IMU", "状态异常"))

# GPS 检查
gps = components.get("gps", {})
if not gps.get("satellites"):
errors.append(ComponentCheckError("GPS", "卫星信号缺失"))
if gps.get("hdop", 99) > 2.0:
errors.append(ComponentCheckError("GPS", "精度因子过高"))

# 电机检查
motors = components.get("motors", [])
for i, motor in enumerate(motors):
if motor.get("rpm", 0) < 0:
errors.append(ComponentCheckError(f"MOTOR-{i+1}", "转速为负"))

# 一次性抛出所有错误
if errors:
raise SystemCheckError("系统检查失败", errors)


# 用 except* 按类型处理
def run_system_check(components: dict) -> bool:
try:
validate_flight_system(components)
except* ComponentCheckError as eg:
# eg.exceptions 是所有 ComponentCheckError 列表
print(f"发现 {len(eg.exceptions)} 个组件错误:")
for exc in eg.exceptions:
print(f" - {exc.component_id}: {exc.reason}")
return False
except* Exception as eg:
print(f"未预期错误:{eg.exceptions}")
return False
else:
print("系统检查通过!")
return True


# 测试:一次显示所有错误
run_system_check({
"imu": {"status": "fault"},
"gps": {"hdop": 3.5},
"motors": [{"rpm": -100}, {"rpm": 1000}],
})
# 输出:
# 发现 3 个组件错误:
# - IMU: 状态异常
# - GPS: 精度因子过高
# - MOTOR-1: 转速为负
对比:抛一个 vs 抛一组

传统做法遇到第一个错误就 raise,用户每次只能看到一个错误,需要反复提交。用 ExceptionGroup 一次性收集所有错误,用户体验显著提升——尤其在系统检查、批量数据处理场景中。

如果只关心单个错误,传统 raise ValueError(...) 仍然是最简单的方案。ExceptionGroup 适合"需要一次看全部"的场景。

最佳实践

1. 异常类一定要继承 Exception(或更具体的子类)

Python
# ❌ 反例:继承 BaseException
class MyError(BaseException):
pass

# 这样写后,except Exception 兜不住它,容易造成意外崩溃


# ✅ 正例:继承 Exception 或更具体的内置异常
class MyError(Exception):
pass

class MyValueError(ValueError): # 如果语义上是"值错误"
pass

2. 总是提供清晰的错误信息

Python
# ❌ 反例:无信息
raise ValidationError()

# ❌ 反例:模糊
raise ValidationError("错误")

# ✅ 正例:信息具体、可操作
raise ValidationError(f"传感器 {sensor_id} 读数 {reading} 超出有效范围")

3. 自定义 str 但保留 args

Python
class ComponentError(Exception):
def __init__(self, component_id: str, reason: str):
self.component_id = component_id
self.reason = reason
# 调用 super().__init__ 让 args 保留原始信息
super().__init__(component_id, reason)

def __str__(self) -> str:
return f"组件 {self.component_id}{self.reason}"


e = ComponentError("IMU-001", "校准失败")
print(str(e)) # 输出:组件 IMU-001:校准失败
print(e.args) # 输出:('IMU-001', '校准失败')
print(e.component_id) # 输出:IMU-001

4. 不要用异常做流程控制

Python
# ❌ 反例:用异常做循环退出
def find_faulty_component(components):
try:
for comp in components:
if comp["status"] == "fault":
raise StopIteration(comp["id"]) # 滥用异常!
except StopIteration as e:
return e.value

# ✅ 正例:用普通 return
def find_faulty_component(components):
for comp in components:
if comp["status"] == "fault":
return comp["id"]
return None

异常应表示真正的异常情况,而不是普通的控制流分支。滥用异常会让代码可读性下降、性能下降(异常处理比条件判断慢得多)。

5. 在合适的层次处理异常

Python
# ❌ 反例:底层捕获并吞掉,上层无法感知
def save_telemetry(data):
try:
db.insert(data)
except DatabaseError:
pass # 静默吞掉,bug 不会被及时发现

# ✅ 正例:底层转换异常,上层决定处理方式
def save_telemetry(data):
try:
db.insert(data)
except DatabaseError as e:
log.error("遥测数据保存失败", exc_info=True)
raise TelemetrySaveError("数据保存失败,请重试") from e

6. 优先用 except 的"具体类型"而非 isinstance

Python
class FlightSystemError(Exception):
pass

class NotFoundError(FlightSystemError):
pass

# ✅ 用 except 区分类型
try:
do_work()
except NotFoundError:
handle_not_found()
except FlightSystemError:
handle_other()

# ❌ 不要用 isinstance(更啰嗦,且无法捕获异常)
try:
do_work()
except FlightSystemError as e:
if isinstance(e, NotFoundError):
handle_not_found()
else:
handle_other()

7. 用 ExceptionGroup 替代"循环收集 + 末尾抛出"

如果代码中有这样的模式:

Python
# 旧模式:手动收集再抛
errors = []
for item in items:
try:
process(item)
except SomeError as e:
errors.append(e)
if errors:
raise AggregatedError(errors)

可以考虑用 Python 3.11+ 的 ExceptionGroup 直接表达:

Python
# 新模式:用 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)

实战:飞行系统异常体系

下面构建一个简化的飞行系统异常体系,演示完整的自定义异常设计:

Python
from __future__ import annotations
import numpy as np


# ========== 异常体系定义 ==========

class FlightSystemError(Exception):
"""飞行系统所有错误的基类。

所有飞行相关异常都继承自此,便于统一捕获和日志记录。

Attributes:
component_id: 受影响的组件标识符。
"""

def __init__(self, message: str, component_id: str | None = None):
self.component_id = component_id
self.message = message
super().__init__(self._format())

def _format(self) -> str:
prefix = f"[{self.component_id}] " if self.component_id else ""
return f"{prefix}{self.message}"


class SensorError(FlightSystemError):
"""传感器相关错误"""


class IMUError(SensorError):
"""惯性测量单元错误"""

def __init__(self, component_id: str, reason: str, readings: np.ndarray | None = None):
self.readings = readings
self.reason = reason
super().__init__(
message=f"IMU 错误:{reason}",
component_id=component_id,
)


class GPSError(SensorError):
"""GPS 错误"""

def __init__(self, component_id: str, satellites: int, hdop: float):
self.satellites = satellites
self.hdop = hdop
super().__init__(
message=f"GPS 信号弱:卫星数 {satellites},HDOP {hdop:.1f}",
component_id=component_id,
)


class ActuatorError(FlightSystemError):
"""执行器相关错误"""


class MotorError(ActuatorError):
"""电机错误"""

def __init__(self, component_id: str, rpm: float, reason: str):
self.rpm = rpm
self.reason = reason
super().__init__(
message=f"电机故障:转速 {rpm:.0f} RPM,{reason}",
component_id=component_id,
)


class ControllerError(FlightSystemError):
"""控制器相关错误"""


class NavigationError(FlightSystemError):
"""导航相关错误"""


class WaypointNotFoundError(NavigationError):
"""航点不存在"""

def __init__(self, waypoint_id: str):
super().__init__(
message=f"航点 {waypoint_id} 不存在",
component_id=waypoint_id,
)


class PathPlanningError(NavigationError):
"""路径规划错误"""

def __init__(self, reason: str, obstacle_count: int = 0):
self.obstacle_count = obstacle_count
super().__init__(
message=f"路径规划失败:{reason}(障碍物数量:{obstacle_count})",
component_id="NAV",
)


# ========== 业务逻辑实现 ==========

class FlightComponent:
def __init__(self, component_id: str, component_type: str, status: str = "ok"):
self.component_id = component_id
self.component_type = component_type
self.status = status

def __repr__(self) -> str:
return f"FlightComponent(id={self.component_id!r}, type={self.component_type!r}, status={self.status!r})"


class FlightInventory:
"""飞行组件管理"""

def __init__(self):
self._components: dict[str, FlightComponent] = {}

def add_component(self, component_id: str, component_type: str, status: str = "ok") -> FlightComponent:
"""添加新组件"""
if component_id in self._components:
raise FlightSystemError(f"组件 {component_id} 已存在", component_id)
if status not in ("ok", "degraded", "fault"):
raise FlightSystemError(f"无效状态:{status}", component_id)
component = FlightComponent(component_id, component_type, status)
self._components[component_id] = component
return component

def get_component(self, component_id: str) -> FlightComponent:
"""查询组件"""
if component_id not in self._components:
raise FlightSystemError(f"组件 {component_id} 不存在", component_id)
return self._components[component_id]

def update_status(self, component_id: str, new_status: str) -> str:
"""更新组件状态"""
component = self.get_component(component_id) # 可能抛 FlightSystemError
if new_status not in ("ok", "degraded", "fault"):
raise FlightSystemError(f"无效状态:{new_status}", component_id)
old_status = component.status
component.status = new_status
return old_status

def check_system_health(self) -> dict[str, str]:
"""检查所有组件状态"""
health = {}
for comp_id, comp in self._components.items():
health[comp_id] = comp.status
return health


# ========== 演示使用 ==========

def demo_flight_system():
inv = FlightInventory()

# 添加组件
inv.add_component("IMU-001", "imu", "ok")
inv.add_component("GPS-001", "gps", "ok")
inv.add_component("MOTOR-1", "motor", "ok")
print("初始组件:", list(inv._components.values()))

# 1. 组件不存在
print("\n--- 查询不存在的组件 ---")
try:
inv.get_component("ESC-999")
except FlightSystemError as e:
print(f"捕获:{e}")

# 2. 重复添加组件
print("\n--- 重复添加组件 ---")
try:
inv.add_component("IMU-001", "imu", "ok")
except FlightSystemError as e:
print(f"捕获:{e}")

# 3. 无效状态
print("\n--- 无效状态 ---")
try:
inv.update_status("MOTOR-1", "invalid")
except FlightSystemError as e:
print(f"捕获:{e}")

# 4. 按层次捕获:父类捕获也能命中子类
print("\n--- 父类统一捕获 ---")
try:
inv.update_status("IMU-001", "fault")
except FlightSystemError as e: # 兜住所有飞行系统异常
print(f"捕获:{e}")

# 5. 正常操作
print("\n--- 正常操作 ---")
old_status = inv.update_status("GPS-001", "degraded")
print(f"GPS-001 状态变更:{old_status} -> degraded")
health = inv.check_system_health()
print(f"系统健康状态:{health}")


# ========== 批量组件检查(使用 ExceptionGroup)==========

def check_component_batch(
inv: FlightInventory, checks: list[tuple[str, str]]
) -> dict[str, str | FlightSystemError]:
"""批量检查组件状态,每个组件独立检查,失败的留下错误信息"""
results: dict[str, str | FlightSystemError] = {}
errors: list[Exception] = []

for component_id, expected_status in checks:
try:
component = inv.get_component(component_id)
if component.status != expected_status:
results[component_id] = f"状态不匹配:期望 {expected_status},实际 {component.status}"
else:
results[component_id] = "正常"
except FlightSystemError as e:
results[component_id] = f"失败:{e}"
errors.append(e)

# 如果有错误,把所有错误打包成 ExceptionGroup 抛出
# 同时仍返回部分成功的结果(通过 results 字典)
if errors:
# 这里演示同时返回结果和抛出异常的混合模式
print(f"批量检查完成,{len(errors)} 个失败:")
for comp_id, result in results.items():
print(f" {comp_id}: {result}")
raise ExceptionGroup(f"{len(errors)} 个组件检查失败", errors)

return results


def demo_batch_check():
inv = FlightInventory()
inv.add_component("IMU-001", "imu", "ok")
inv.add_component("GPS-001", "gps", "degraded")
inv.add_component("MOTOR-1", "motor", "ok")

checks = [
("IMU-001", "ok"), # 正常
("GPS-001", "ok"), # 状态不匹配
("ESC-001", "ok"), # 组件不存在
("MOTOR-1", "ok"), # 正常
("IMU-001", "fault"), # 状态不匹配
]

print("\n--- 批量组件检查 ---")
try:
results = check_component_batch(inv, checks)
print("全部正常:", results)
except* FlightSystemError as eg:
for exc in eg.exceptions:
print(f" 组件错误:{exc}")
except* Exception as eg:
# 兜底其他异常
for exc in eg.exceptions:
print(f" 其他错误:{exc}")


demo_flight_system()
demo_batch_check()

输出示例:

输出
初始组件: [FlightComponent(id='IMU-001', type='imu', status='ok'), FlightComponent(id='GPS-001', type='gps', status='ok'), FlightComponent(id='MOTOR-1', type='motor', status='ok')]

--- 查询不存在的组件 ---
捕获:[ESC-999] 组件 ESC-999 不存在

--- 重复添加组件 ---
捕获:[IMU-001] 组件 IMU-001 已存在

--- 无效状态 ---
捕获:[MOTOR-1] 无效状态:invalid

--- 父类统一捕获 ---
捕获:[IMU-001] IMU 错误:状态变更失败

--- 正常操作 ---
GPS-001 状态变更:ok -> degraded
系统健康状态:{'IMU-001': 'fault', 'GPS-001': 'degraded', 'MOTOR-1': 'ok'}

--- 批量组件检查 ---
批量检查完成,2 个失败:
IMU-001: 正常
GPS-001: 失败:[GPS-001] GPS 信号弱:卫星数 0,HDOP 0.0
ESC-001: 失败:[ESC-001] 组件 ESC-001 不存在
MOTOR-1: 正常
IMU-001: 失败:[IMU-001] IMU 错误:状态变更失败
组件错误:[GPS-001] GPS 信号弱:卫星数 0,HDOP 0.0
组件错误:[ESC-001] 组件 ESC-001 不存在
组件错误:[IMU-001] IMU 错误:状态变更失败
注意批量处理的细节

上面 check_component_batch 的实现有个微妙之处:IMU-001 第一次检查成功了,第二次检查失败。所以 IMU-001 在 results 中显示的是失败信息(最后一次的结果覆盖了成功值)。在生产代码中,应当区分"成功记录"和"失败记录"两个集合,避免这种歧义。

ExceptionGroup 让我们能在一次调用中报告所有失败,而 except* 让上层按异常类型分别处理——这是异常处理从"单错误"到"多错误"的重要演进。

🎯 动手练习

  1. 飞行系统异常体系:为无人机系统设计异常层次,包含 SensorErrorActuatorErrorNavigationError 及其子类
  2. 组件验证:实现 ComponentValidationError,携带 component_id、component_type、reason 属性,用于组件数据验证
  3. 异常链实践:编写硬件通信函数,将 ConnectionError 转换为 CommunicationError,使用 raise ... from ... 保留上下文
  4. 批量检查:使用 ExceptionGroup 实现飞行系统检查,一次性收集所有组件错误

📚 延伸阅读

  • 上下文管理器with 语句自动管理资源,替代 finally 块
  • Pydantic 验证:第三方库,提供运行时数据校验和类型强制转换
  • 错误码设计:RESTful API 错误响应标准,HTTP 状态码与业务错误码映射
  • 日志记录logging.exception() 自动记录异常堆栈,生产环境必备
📋速查表
定义异常`class X(Exception):`
class ValidationError(Exception):
继承内置异常`class X(ValueError):`
class InvalidSensorError(ValueError):
调用父类`super().__init__(*args)`
保留标准行为
自定义展示`def __str__(self):`
格式化错误信息
额外属性`self.field = value`
携带结构化上下文
错误码`code = "ERR_CODE"`
便于程序化处理
异常链`raise X from Y`
保留 __cause__
抑制链`raise X from None`
隐藏原始异常
异常组`ExceptionGroup(msg, [e1, e2])`
批量收集错误
拆解异常组`except* Type:`
3.11+ 语法
层次捕获`except ParentError:`
捕获所有子类

✅ 本节总结

本节我们学习了自定义异常的设计,核心要点包括:

  • 继承 Exception:自定义异常继承 Exception 或更具体的内置异常,不要继承 BaseException
  • 命名规范:以 Error/Exception 结尾,名字反映问题本身而非触发场景
  • 保留标准行为:调用 super().__init__(*args) 让异常信息存入 args
  • 额外属性:通过属性携带结构化上下文(错误码、组件ID、原始值),便于程序化处理
  • 异常层次:顶层基类 → 中层分组 → 叶子具体异常,便于按层次捕获和扩展
  • 异常链raise X from Y 建立 __cause__ 链,转换异常时保留底层上下文
  • ExceptionGroup:3.11+ 引入,适合批量收集错误一次抛出,配合 except* 分别处理
  • 最佳实践:清晰的错误信息、不在底层静默吞异常、不用异常做流程控制

下一节将学习上下文管理器——一种把"资源获取"和"资源释放"自动配对的优雅机制。