Python 报错处理(Exception Handling)完整教程#
目标
通过本教程,读者能够:
- 明白 Python 报错(异常)的基本概念与产生机制。
- 熟练使用
try/except/finally、自定义异常、日志记录等三种常见的错误处理方案。- 能够快速定位错误、避免程序崩溃,并把错误信息写入日志或展示给用户。
一、原因分析#
| 错误类型 | 典型场景 | 产生原因 | 典型错误信息 |
|---|---|---|---|
SyntaxError |
代码写法错误 | 语法不符合 Python 语法 | invalid syntax |
ImportError |
模块/包导入失败 | 模块不存在、路径错误 | No module named 'xxx' |
NameError |
未定义变量 | 变量未声明或拼写错误 | name 'foo' is not defined |
TypeError |
参数类型不匹配 | 传入了错误类型的参数 | unsupported operand type(s) |
ValueError |
参数值错误 | 传入了无效但类型正确的值 | invalid literal for int() with base 10 |
IOError/OSError |
文件/网络操作 | 文件不存在、权限不足、网络异常 | FileNotFoundError, ConnectionError |
KeyError |
访问字典不存在的 key | key 不在字典里 | KeyError: 'foo' |
IndexError |
列表/元组越界 | 访问了不存在的索引 | list index out of range |
注意:所有异常最终都会生成一个“Traceback”,它记录了异常发生的调用栈,便于定位问题。
二、三种分步骤的解决方案#
下面分别介绍三种常用的错误处理方法。每个方法都配有步骤和代码示例,可按需选择。
1️⃣ 方案一:基础 try/except/finally#
适用于大多数场景,可以捕获并处理错误,保证程序继续执行。
步骤#
- 识别可能抛异常的代码段(如 I/O、网络请求、第三方库调用等)。
- 使用
try:包装该段代码。 - 用
except ExceptionType:捕获具体异常,或用except Exception:捕获所有异常。 - 在
except块中记录错误或给用户友好提示。 - (可选)使用
finally:执行无论是否异常都需要执行的清理代码(如关闭文件、释放资源)。
代码示例#
import os
def read_file(path):
try:
with open(path, 'r') as f:
data = f.read()
return data
except FileNotFoundError as e:
print(f"文件未找到: {path}")
# 记录日志
# logging.error(e)
except PermissionError:
print("没有足够的权限读取文件")
finally:
print("文件读取尝试完成")
# 调用
content = read_file("non_existent.txt")要点
- 不要捕获所有异常 (
except:),否则会掩盖隐藏的 bug。finally:用来保证资源释放。
2️⃣ 方案二:使用 日志 记录错误#
当程序需要持续运行、或错误需要持久化记录(如后台服务),建议将错误写入日志文件。
步骤#
- 配置
logging模块(文件、级别、格式)。 - 在
except块中使用logging.exception()或logging.error()记录异常。 - (可选)根据业务需求,决定是否继续执行或抛出自定义异常。
代码示例#
import logging
import json
logging.basicConfig(
filename='app.log',
level=logging.ERROR,
format='%(asctime)s %(levelname)s %(message)s'
)
def load_config(path):
try:
with open(path, 'r') as f:
return json.load(f)
except json.JSONDecodeError as e:
logging.exception(f"配置文件解析错误: {path}")
# 重新抛出,或者返回默认配置
raise
except FileNotFoundError:
logging.error(f"配置文件不存在: {path}")
return {}
config = load_config('config.json')要点
logging.exception()会自动把 traceback 信息写入日志。- 结合
logging的不同级别(DEBUG/INFO/WARNING/ERROR/CRITICAL)可以细化日志策略。
3️⃣ 方案三:自定义异常 与 层级化错误处理#
对于复杂项目,最好定义自己的异常类,方便调用者根据业务层次做不同处理。
步骤#
- 定义自定义异常类,继承自
Exception或更细粒度的基类。 - 在业务函数内部抛出该异常,并附带有意义的错误信息。
- 在高层调用处捕获自定义异常,决定是否重试、提示用户或最终退出。
代码示例#
# my_exceptions.py
class ValidationError(Exception):
"""数据校验错误"""
pass
class ServiceError(Exception):
"""后端服务异常"""
pass
# business.py
from my_exceptions import ValidationError, ServiceError
import requests
def fetch_user(user_id: int) -> dict:
if not isinstance(user_id, int):
raise ValidationError("用户 ID 必须是整数")
try:
resp = requests.get(f"https://api.example.com/user/{user_id}")
resp.raise_for_status()
return resp.json()
except requests.RequestException as err:
raise ServiceError(f"获取用户失败: {err}") from err
# main.py
from business import fetch_user
from my_exceptions import ValidationError, ServiceError
def main():
try:
user = fetch_user("abc") # 故意传错类型
except ValidationError as ve:
print(f"验证错误: {ve}")
except ServiceError as se:
print(f"服务错误: {se}")
else:
print(user)
if __name__ == "__main__":
main()要点
- 使用
raise ... from ...链接原始异常,保持 traceback。- 自定义异常可以携带更多上下文信息,方便日志分析。
三、常见问题解答(FAQ)#
| # | 问题 | 解决方案 |
|---|---|---|
| 1 | 异常捕获后程序仍然崩溃 | 检查是否在 except 后又 raise 了同样的异常,或没有 except 代码块覆盖到。 |
| 2 | 日志文件没有写入 | 确认 logging.basicConfig() 的 filename 路径可写,或检查文件权限。 |
| 3 | 捕获 Exception 后仍然看到原始 Traceback |
可能在 except 里又 raise 了异常。若想完全隐藏,请使用 except Exception: 并不再 re‑raise。 |
| 4 | 自定义异常未被捕获 | 确认 except CustomError: 与自定义异常类的命名、导入路径一致。 |
| 5 | 无法在 finally 中访问异常对象 |
在 except 里可将异常对象赋值给外部变量,或使用 sys.exc_info()。 |
| 6 | 多线程/异步程序中的异常 | 线程内部异常不会自动传播到主线程;需要使用 concurrent.futures 的 future.exception() 或在 async 函数中 try/except。 |
| 7 | 全局异常处理 | 在入口文件使用 sys.excepthook 或 logging 的 logging.basicConfig 统一捕获未处理异常。 |
| 8 | 异常堆栈过长导致日志文件膨胀 | 在生产环境中可使用 logging.handlers.RotatingFileHandler 或 TimedRotatingFileHandler,并限制日志级别。 |
| 9 | 如何在单元测试中验证异常 | 使用 pytest.raises 或 unittest.assertRaises。 |
| 10 | 异常信息中包含敏感数据 | 在捕获后,先“清洗”异常信息再写日志或返回给用户。 |
四、总结#
- 先定位,再处理:先看错误信息、traceback,确定触发点。
- 层级化错误处理:从底层抛出自定义异常,再由业务层解码处理。
- 日志是最好的伙伴:无论是开发还是运维,持久化错误堆栈是排查问题的关键。
- 避免“吞噬”异常:不要随意捕获所有异常;至少要记录后再抛出或给出明确提示。
掌握了上述三种方案,你就能在大多数 Python 项目中优雅地处理错误,并保持代码的可维护性与健壮性。祝你编码愉快 🚀