Python 报错跳过(Error Skipping)完整教程#
目标:帮助你快速定位为什么代码会报错、如何在执行过程中“跳过”错误并继续往下跑,以及常见的坑与解决办法。
1. 引言#
在日常数据清洗、网络爬虫、并行计算等任务中,不想因为单个异常就导致整个程序崩溃。Python 的 try...except 机制让我们可以灵活地处理错误,但往往写得不够优雅、缺乏可维护性。本文从原因分析出发,给出 3 种分步骤的解决方案,并附带常见问题解答,助你在任何脚本中实现“报错跳过”。
说明:以下示例均使用 Python 3.10+,但大部分代码在 3.8/3.9 也能正常运行。
2. 原因分析#
| 场景 | 典型错误 | 产生原因 | 影响 |
|---|---|---|---|
| 文件读取 | FileNotFoundError, PermissionError |
路径错误、文件不存在、无读写权限 | 终止读取流程 |
| 网络请求 | requests.exceptions.ConnectionError, Timeout |
网络不稳定、服务器不可达 | 请求失败,后续流程中断 |
| 数据转换 | ValueError, TypeError |
字符串无法转为数值、类型不匹配 | 解析失败导致整个批次终止 |
| 数据库操作 | sqlite3.IntegrityError, OperationalError |
唯一约束冲突、连接中断 | 事务失败,后面依赖数据缺失 |
关键点:错误往往是可预期的 异常情况,不是完全不可处理。我们需要在代码层面判断、捕获并决定是否“跳过”。
3. 3 种分步骤的解决方案#
方案 A:基础 try...except + continue#
最常见、最直观。适用于循环内部的单个处理单元。
for item in data_list:
try:
process(item) # 可能抛异常的逻辑
except (ValueError, TypeError) as e:
print(f"[WARN] 处理 {item} 失败: {e}")
continue # 跳过该条记录,继续下一条
except Exception as e:
# 捕获不可预期异常,记录后继续
log.exception(f"不可预期错误: {e}")
continue优点:代码简洁,错误信息可直接打印/记录。
缺点:对异常类型需要手动列举,易漏。
步骤拆解#
- 包裹目标逻辑:将可能抛异常的代码放在
try块内。 - 细化异常捕获:按业务需求列出具体异常;使用
Exception捕获所有。 - 记录与跳过:在
except内记录信息(print、logging),然后continue或pass。
方案 B:使用 contextlib.suppress#
当你想抑制特定异常而不打断程序时,suppress 是最轻量的方案。
from contextlib import suppress
with suppress(FileNotFoundError, PermissionError):
with open("config.json", "r") as f:
config = json.load(f)
# 程序继续执行优点:不需要手写
try...except,代码更简洁。
缺点:无法自定义日志,所有被抑制的异常会被“吞掉”,不易追踪。
步骤拆解#
- 导入
suppress。 - 列出想抑制的异常。
- 将可抛异常的代码放在
with suppress(...)块中。
适用于“如果文件不存在就用默认值,直接忽略”这类场景。
方案 C:自定义错误处理装饰器#
如果你在多处需要相同的错误跳过逻辑,写一个装饰器可以大幅提升可维护性。
import functools
import logging
def skip_errors(*exc_classes, log_level=logging.WARNING):
"""装饰器:捕获指定异常,记录日志后返回 None"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except exc_classes as e:
logging.log(log_level, f"跳过 {func.__name__} 出错: {e}")
return None
return wrapper
return decorator
# 用法
@skip_errors(ValueError, TypeError)
def parse_int(s: str):
return int(s)
result = parse_int("abc") # 记录警告,返回 None优点:可复用且易维护,所有错误信息集中记录。
缺点:如果函数返回None与业务意义冲突,需要注意。
步骤拆解#
- 定义装饰器:接收要捕获的异常类型。
- 包装函数:使用
try...except统一处理。 - 调用:在需要跳过错误的地方使用
@skip_errors。
4. 常见问题解答(FAQ)#
| 问题 | 说明 | 解决方法 |
|---|---|---|
Q1. 为什么 except Exception 也会导致程序退出? |
可能 except 里没有 continue 或 return,导致后续代码不执行。 |
确认 except 内部有跳过逻辑(continue/pass)。 |
| Q2. 如何在跳过错误时保留错误堆栈信息? | 只打印错误会丢失堆栈。 | 使用 logging.exception() 或 traceback.print_exc()。 |
Q3. contextlib.suppress 也能记录错误吗? |
默认不会记录。 | 自定义 suppress 或在 except 块中手动记录。 |
| Q4. 在多线程/async 中怎么跳过错误? | try...except 需要放在每个线程/协程里。 |
在 async def 内部使用 try...except,或者使用 asyncio.gather(..., return_exceptions=True)。 |
| Q5. 跳过错误后如何继续批量处理? | 在循环里使用 continue 或 return。 |
在 except 内部调用 continue 以跳过当前迭代,或 return 终止子函数。 |
| Q6. 跳过错误会导致数据不完整,如何追踪缺失记录? | 记录被跳过的索引或 ID。 | 在日志里输出 item_id,或者写入 failed_records.txt。 |
| Q7. 是否会出现“错误未被捕获”的情况? | 如果 except 里没有列出所有可能的异常。 |
先写 except Exception 捕获所有,再细化需要的异常。 |
| Q8. 如何在批量写入数据库时跳过冲突记录? | INSERT OR IGNORE 或 ON CONFLICT DO NOTHING。 |
在 SQL 语句中使用冲突处理,或者在 except sqlite3.IntegrityError 时 continue。 |
5. 小结#
- 错误跳过 是一个 “容错” 的设计理念。
- 选择合适的方案取决于 执行粒度(单条记录 vs 整个文件)与 错误类型(可预期 vs 随机)。
- 始终记录错误,即使你在生产环境中不想让它阻塞业务。
- 对于 大规模处理,建议使用 装饰器 或 错误日志文件,避免在控制台打印过多信息。
实践建议:先在本地调试时使用
logging并设置合适的日志级别;并在错误日志里记录 行号、异常类型 与 上下文,方便后期排查。祝你写出既稳健又可维护的 Python 程序!