异常处理与错误边界
学习目标
- 使用
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__)
# 输出:ValueErrorfrom 保留底层原因,同时向调用方暴露更合适的领域异常。
不常用但需要知道
assert
python
def only_even(value: int) -> int:
assert value % 2 == 0, "value must be even"
return value
print(only_even(4))
# 输出:4assert 用于开发期不变量,运行 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不是生产输入校验。- 异常消息用于说明,异常类型用于程序分支。
课后小问
为什么把
try包住最小语句更好? 答案:可以准确知道哪些操作允许失败,避免把处理逻辑自己的 bug 误认为输入错误。 解析:try过大时,except会吞掉不该恢复的异常,调试成本明显增加。什么时候使用
raise ... from error? 答案:把底层异常转换为更适合当前边界的领域异常,同时保留原始原因。 解析:异常链让日志既有业务语义,又能追溯原始错误。
本节小结
异常处理的目标是明确错误边界和恢复策略:精确捕获、合理转换、保留上下文,避免静默吞错。
快速回顾
- 结构:
try、except、else、finally - 主动失败:
raise - 业务语义:自定义
Exception子类 - 错误链:
raise ... from ... - 断言边界:
assert不替代输入校验
阿源的知识库