readlines()会卡死是因为全量加载导致内存耗尽、频繁swap甚至被OOM killer终止,而for line in file基于迭代器协议惰性读取,内存恒定在KB级。

为什么直接用 readlines() 会卡死
因为 readlines() 会把整个文件一次性加载进内存,一个 10GB 的日志文件可能瞬间吃光几 GB 内存,触发系统频繁 swap,甚至被 OOM killer 杀掉进程。这不是“慢”,是资源耗尽导致的假死。
- 真实现象:Python 进程 CPU 占用低、内存暴涨、磁盘 I/O 持续高位、
Ctrl+C也响应迟缓 -
read()和readlines()都有同样问题,本质是全量读取 - 即使加了
encoding='utf-8'或errors='ignore',也无法绕过内存瓶颈
用 for line in file 实现真正的逐行迭代
这是最轻量、最可靠的方式——底层由 Python 的迭代器协议驱动,每次只读入一行(直到遇到 \n),内存占用恒定在 KB 级别。
- 必须用
with open(...)上下文管理器,否则异常时文件句柄可能泄漏 - 避免手动调用
file.readline(),它在空行或末尾容易多一次无效调用,逻辑更难控制 - 如果日志含混合编码(比如部分 GBK 日志),建议先用
chardet探测,再指定encoding,否则UnicodeDecodeError会中断迭代
with open('/var/log/app.log', 'r', encoding='utf-8', errors='replace') as f:
for line in f: # ✅ 真正的惰性迭代
if 'ERROR' in line:
print(line.rstrip())
需要跳过前 N 行或按块处理怎么办
不要用 list(f)[:N] 或 itertools.islice(f, N) 后再循环——前者又变相加载,后者虽不加载全部,但 islice 本身会消耗迭代器,后续 for line in f 就拿不到数据了。
- 跳过前 N 行:用
next()手动推进,配合enumerate更安全 - 按固定行数分块(如每 1000 行做一次统计):用
itertools.islice(f, 1000)做局部消费,且每次新开islice不影响原文件对象 - 注意:Windows 下换行符是
\r\n,但 Python 默认 universal newlines 模式已兼容,无需额外处理
# 跳过前 100 行
with open('big.log') as f:
for _ in range(100):
next(f, None) # 第二个参数防止 StopIteration
for line in f:
process(line)
<h1>每 500 行一批处理</h1><p>import itertools
with open('big.log') as f:
while True:
batch = list(itertools.islice(f, 500))
if not batch:
break
aggregate(batch)
</p>
遇到超长行(如单行 JSON 日志)怎么防崩
有些日志框架会把整个请求体打成一行,长度达数 MB。这时 for line in f 仍会卡住——不是内存爆了,是 Python 在内部缓冲区里疯狂找 \n,导致单次 next() 调用耗时极长。
- 用
file.readline(size)限制单次读取上限,例如f.readline(1024*1024)最多读 1MB - 若返回字符串末尾不含
\n,说明该行超长,可记录告警或丢弃,避免阻塞后续处理 - 不要用
io.BufferedReader的readline()替代,它不支持size参数;必须用普通TextIOWrapper对象(即默认open()返回的对象)
with open('big.log') as f:
while True:
line = f.readline(2 * 1024 * 1024) # 限制 2MB
if not line:
break
if not line.endswith('\n'):
print('⚠️ 超长行截断,长度:', len(line))
else:
process(line)
逐行迭代本身很简单,但实际日志场景里的编码混杂、行宽失控、权限抖动、NFS 挂载延迟等问题,会让看似稳定的 for line in f 突然卡住或报错。真正稳住的关键,不是“怎么读”,而是“读到异常时,是否还在可控路径上”。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











