异常处理 - try/except/else/finally 全面指南
系统讲解 Python 异常处理的定义、常见异常类型、最佳实践与完整代码示例,帮助你编写健壮、可维护的代码。
异常处理 - try/except/else/finally 全面指南
📝 概述
异常(Exception)是程序运行过程中出现的错误状态。当出现异常且未被处理时,程序会中断并输出回溯信息(traceback)。合理的异常处理能让程序在面对错误时做出可控响应:记录日志、释放资源、回滚状态、向上抛出或降级处理。
🎯 学习目标
- 理解异常处理的基本概念、传播机制与栈回溯
- 掌握 try/except/else/finally 的语义与用法
- 熟悉常见内置异常类型及适用场景
- 学会编写自定义异常,并进行异常链式抛出与日志记录
- 掌握编写健壮代码的异常处理最佳实践
📋 前置知识
- 基本的 Python 语法与函数调用
- 文件/网络等资源的打开与关闭
- 日志记录与上下文管理(with)
🔍 详细内容
异常处理的定义与传播
- 异常是 Python 在运行时对错误条件的对象化表示(都是从 BaseException 派生的实例)。
- 当代码块抛出异常且当前帧未捕获时,异常会沿调用栈逐层向上“传播”,直到被某一层捕获,或者到达顶层导致程序终止并打印 traceback。
- 捕获异常的目标不是“吞掉错误”,而是做恰当处理:记录、清理、重试、转换为领域错误后再抛出等。
try/except/else/finally 的语义
## 基本结构示例:
try:
# 可能抛出异常的代码
risky_operation()
except SpecificError as e:
# 处理具体异常
handle(e)
except (TypeError, ValueError) as e:
# 捕获多个异常类型
handle_multi(e)
except Exception as e:
# 最后兜底(不建议直接 except:)
log_error(e)
else:
# try 代码块未抛异常时执行(成功路径)
on_success()
finally:
# 无论是否异常都执行(资源清理)
cleanup()
- except:从最具体到最通用依次排列;避免裸 except:,否则会拦截诸如 KeyboardInterrupt 等控制流异常。
- else:成功路径逻辑放在 else,有助于将正常流程与错误处理分离,提升可读性。
- finally:保证清理动作必定执行(关闭文件、释放锁、恢复环境变量等)。
常见内置异常类型与场景
- ValueError:值不合法(如 int(“abc”))
- TypeError:类型不匹配(如 1 + “a”)
- KeyError/IndexError:字典键或序列索引不存在
- FileNotFoundError/PermissionError:文件不存在或权限不足
- OSError/IOError:操作系统/IO 层面的通用错误
- ZeroDivisionError:除以零
- AttributeError:对象属性不存在
- ImportError/ModuleNotFoundError:导入失败/模块不存在
- AssertionError:断言失败(用于开发期校验,不用于业务控制)
- RuntimeError/NotImplementedError:运行时错误/未实现分支
- TimeoutError:超时
- KeyboardInterrupt/SystemExit:用户中断/系统退出(通常不应拦截)
自定义异常
## 定义层次化的自定义异常,便于精准捕获与语义表达
class AppError(Exception):
"""应用通用异常基类"""
class ConfigError(AppError):
"""配置不合法"""
class NetworkError(AppError):
"""网络相关错误"""
## 使用示例
def load_config(path: str) -> dict:
"""加载配置文件,若格式不合法则抛出 ConfigError"""
import json, os
if not os.path.exists(path):
raise ConfigError(f"配置文件不存在: {path}")
try:
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
except json.JSONDecodeError as e:
# 转换底层异常为领域异常,保留上下文
raise ConfigError(f"配置解析失败: {e}") from e
异常链与回溯保留
## 使用 raise ... from ... 建立异常链,既保留根因回溯,又暴露更贴近业务的错误类型
try:
connect_db()
except TimeoutError as e:
raise NetworkError("数据库连接超时") from e
日志记录与调试
## 记录异常详情与回溯,便于排查问题
import logging, traceback
logger = logging.getLogger(__name__)
try:
do_something()
except Exception as e:
# 打印完整回溯信息
logger.error("执行失败: %s\n%s", e, traceback.format_exc())
# 或使用 logger.exception 在 except 块中直接记录回溯
# logger.exception("执行失败")
raise # 处理后继续向上抛出,避免静默失败
清理资源:finally 与 with
## finally 确保清理
f = open('data.txt', 'w')
try:
f.write('hello')
finally:
f.close() # 无论是否异常都执行
## 推荐使用 with 管理资源
with open('data.txt', 'w') as f:
f.write('hello') # 自动关闭文件,异常也会正确传播
多 except、分组捕获与重新抛出
## 将语义相近的异常合并捕获,避免重复代码
try:
parse_and_write()
except (ValueError, TypeError) as e:
fix_input(e)
except OSError as e:
recover_file(e)
except Exception:
# 兜底只做记录,然后重新抛出
log_unexpected()
raise
断言 assert 的定位
## 断言用于开发期不变量校验,生产环境可能被 -O 优化移除
## 不要用 assert 取代业务异常(如参数校验)
assert isinstance(port, int) and port > 0, "端口必须为正整数"
asyncio 与并发中的异常
## 在协程中捕获并处理,避免任务静默失败
import asyncio
async def worker(i):
try:
# 这里可能抛出异常
return 10 / (i - 3)
except ZeroDivisionError as e:
# 针对特定异常返回降级结果
return float('inf')
async def main():
tasks = [asyncio.create_task(worker(i)) for i in range(6)]
# 返回异常而不是直接失败(按需选择)
results = await asyncio.gather(*tasks, return_exceptions=True)
print(results)
# asyncio.run(main())
## 超时控制
import asyncio
async def op():
await asyncio.sleep(2)
return 42
async def main():
try:
res = await asyncio.wait_for(op(), timeout=1)
except asyncio.TimeoutError:
res = None # 超时降级
print(res)
# asyncio.run(main())
💡 实际应用
参数校验与错误转换
def get_user_age(record: dict) -> int:
"""将底层 KeyError/TypeError 转换为语义更清晰的 ValueError"""
try:
age = record["age"]
if not isinstance(age, int) or age < 0:
raise ValueError("年龄必须为非负整数")
return age
except KeyError as e:
raise ValueError("缺少必需字段: age") from e
重试与降级(示意)
import time
def fetch_with_retry(fetch, retries=3, delay=0.5):
for i in range(retries):
try:
return fetch()
except NetworkError as e:
if i == retries - 1:
raise # 仍抛出,交由上层处理
time.sleep(delay) # 简单重试等待
⚠️ 最佳实践与注意事项
- 只捕获你能处理的异常:未知异常记录后向上抛出,避免静默失败。
- 从具体到通用按序捕获,减少误捕;避免裸 except:。
- 将正常路径逻辑放在 else,清理工作放在 finally 或 with 中。
- 日志记录要包含回溯(logger.exception 或 traceback.format_exc)。
- 自定义异常按层次化组织,向上抛出领域语义更清晰的异常,必要时使用异常链(raise … from …)。
- 不要用异常控制正常流程;也不要滥用 assert 进行业务校验。
- 注意 KeyboardInterrupt/SystemExit 等控制流异常一般不应拦截。
- 在并发与异步中,确保任务的异常不被吞掉(gather 的 return_exceptions、任务回调等)。
- 对资源进行正确的生命周期管理(with/try-finally)。
- 在公共 API 层,提供稳定的异常契约:明确抛出的异常类型与含义。
🔗 相关内容
📚 扩展阅读
- Python 官方教程:Errors and Exceptions
- Python 语言参考:Exceptions
🏷️ 标签
异常处理 try except finally raise assert
最后更新: 2025-08-25
作者: Python 技术文档工程师
版本: 1.0
讨论与反馈
欢迎在下方留言讨论,分享你的学习心得或提出问题。评论基于GitHub Issues,需要GitHub账号。