Skip to content

异常处理与错误边界 ​

学习目标 ​

  • 使用 try/except/else/finally 处理可预期错误。
  • 选择合适的内置异常并定义领域异常。
  • 保留异常上下文,避免“裸 except”吞掉问题。

核心知识点 ​

异常是运行时传递错误信息的控制流。try 放可能失败的最小代码,except 只捕获能处理的类型;else 只在无异常时运行,finally 无论成功失败都会运行。

常用用法 ​

try / except ​

python
def parse_count(text: str) -> int:
    try:
        return int(text)
    except ValueError:
        return 0


print(parse_count("12"), parse_count("unknown"))
# 输出:12 0

捕获异常时给出可恢复的默认值;不要用 except Exception 包裹整个程序后静默返回。

捕获多个异常 ​

python
def safe_ratio(total: float, count: float) -> float | None:
    try:
        return total / count
    except (TypeError, ZeroDivisionError):
        return None


print(safe_ratio(10, 2), safe_ratio(10, 0))
# 输出:5.0 None

多个同样处理的异常可组成元组;不同错误应分别处理并提供不同恢复策略。

else 与 finally ​

python
events = []
try:
    value = int("7")
except ValueError:
    events.append("error")
else:
    events.append(f"ok:{value}")
finally:
    events.append("cleanup")
print(events)
# 输出:['ok:7', 'cleanup']

把成功路径放在 else 可避免 except 误捕获处理代码本身产生的异常。

raise 主动抛出异常 ​

python
def require_positive(value: int) -> int:
    if value <= 0:
        raise ValueError("value must be positive")
    return value


try:
    require_positive(-1)
except ValueError as error:
    print(type(error).__name__, str(error))
    # 输出:ValueError value must be positive

错误消息应说明输入和约束,方便调用者和日志定位问题。

自定义异常 ​

python
class ConfigurationError(ValueError):
    """配置内容不符合业务约束。"""


def load_mode(mode: str) -> str:
    if mode not in {"safe", "fast"}:
        raise ConfigurationError(f"unsupported mode: {mode}")
    return mode


try:
    load_mode("debug")
except ConfigurationError as error:
    print(type(error).__name__)
    # 输出:ConfigurationError

自定义异常让上层按领域语义捕获,而不是依赖错误消息文本。

异常上下文 raise ... from ​

python
class ConfigurationError(ValueError):
    pass


def read_port(text: str) -> int:
    try:
        return int(text)
    except ValueError as error:
        raise ConfigurationError("port must be an integer") from error


try:
    read_port("http")
except ConfigurationError as error:
    print(type(error.__cause__).__name__)
    # 输出:ValueError

from 保留底层原因,同时向调用方暴露更合适的领域异常。

不常用但需要知道 ​

assert ​

python
def only_even(value: int) -> int:
    assert value % 2 == 0, "value must be even"
    return value


print(only_even(4))
# 输出:4

assert 用于开发期不变量,运行 Python 优化模式时可能被移除;用户输入校验必须显式 raise。

BaseException 与 Exception ​

python
handled = issubclass(KeyboardInterrupt, BaseException) and not issubclass(KeyboardInterrupt, Exception)
print(handled)
# 输出:True

通常捕获 Exception,不要捕获 BaseException,否则可能拦截退出和中断信号。

ExceptionGroup ​

python
errors = ExceptionGroup("批量失败", [ValueError("bad value"), TypeError("bad type")])
print(len(errors.exceptions), errors.message)
# 输出:2 批量失败

Python 3.11+ 的 ExceptionGroup 适合并发或批处理同时报告多个独立错误,普通流程不必强行使用。

易混点 ​

  • except: 会捕获 KeyboardInterrupt 等退出信号,几乎总是过宽。
  • finally 中的 return 可能覆盖 try 或 except 的返回/异常,应避免。
  • raise 不带异常只能在 except 中重新抛出当前异常。
  • assert 不是生产输入校验。
  • 异常消息用于说明,异常类型用于程序分支。

课后小问 ​

  1. 为什么把 try 包住最小语句更好? 答案:可以准确知道哪些操作允许失败,避免把处理逻辑自己的 bug 误认为输入错误。 解析:try 过大时,except 会吞掉不该恢复的异常,调试成本明显增加。

  2. 什么时候使用 raise ... from error? 答案:把底层异常转换为更适合当前边界的领域异常,同时保留原始原因。 解析:异常链让日志既有业务语义,又能追溯原始错误。

本节小结 ​

异常处理的目标是明确错误边界和恢复策略:精确捕获、合理转换、保留上下文,避免静默吞错。

快速回顾 ​

  • 结构:try、except、else、finally
  • 主动失败:raise
  • 业务语义:自定义 Exception 子类
  • 错误链:raise ... from ...
  • 断言边界:assert 不替代输入校验