直接 open().readlines() 会内存爆炸,因它一次性将整个文件加载进内存并返回 list,内存占用达文件体积的 2–3 倍;应改用文件对象原生迭代或生成器实现流式处理。

为什么直接 open().readlines() 会内存爆炸
读取几个 GB 的日志或 CSV 文件时,readlines() 会把全部内容一次性加载进内存——不是几 MB,而是和文件大小等量的内存占用。哪怕你只想要其中某几行,Python 也得先把整块数据搬进来再切片。
根本问题在于:它不 lazy,也不流式。而真实场景里,90% 的逐行处理(比如过滤、统计、清洗)根本不需要所有行同时在内存里。
-
readlines()返回的是list,每行字符串自带引用开销,实际内存占用常达文件体积的 2–3 倍 -
read()更糟,整个文件变成一个超长str,还可能触发 Unicode 解码缓冲区膨胀 - 即使加了
encoding='utf-8',解码过程本身也会在内部缓存未完成的字节序列,大文件下 GC 压力明显
用 yield 写生成器,但别手写底层迭代
很多人想“自己 yield 每行”,于是写个循环 + f.readline() + yield。这没错,但没必要——Python 文件对象本身就是可迭代的,且默认就是按行迭代,天然支持 yield 语义。
真正要做的,是把文件对象包进一个函数,用 yield 转发,顺便加逻辑(比如跳过空行、预处理、限流)。这样既保持流式,又可复用。
SkillSub Pro - Python 题解与代码注释双功能技能功能概述SkillSub Pro - Python 题解与代码注释双功能技能是一项面向实际任务的技能,主要用于SkillSub Pro 是一个 Python 题解生成与代码注释的 双功能合体技能 ,专为学生、算法学习者和开发者设计;✅ 一个技能,两种用途 :;核心要点📝 题解模式 :输入题目/题号,自动生成完整 Python 题解(含详细注释、解题思路、复杂度分析);💬 注释模式 :输入 Python 代码,自动添加详细中。它将相关步骤、
def read_large_file(filepath, skip_empty=True):
with open(filepath, encoding='utf-8') as f:
for line in f: # ← 这里就是最轻量的按行迭代,底层调用 f.readline()
line = line.rstrip('\n\r')
if skip_empty and not line:
continue
yield line
<h1>使用</h1><p>for line in read_large_file('access.log'):
if '404' in line:
print(line)
</p>
- 不要用
f.read(8192)手动分块再 split('\n')——边界行会被截断,还得拼接,bug 隐患高 - 务必用
with open,否则生成器中途异常退出,文件句柄可能泄露 - 如果文件含 BOM(如 UTF-8-SIG),建议显式传
encoding='utf-8-sig',否则首行可能多出\ufeff
遇到编码错误怎么办:不是忽略,而是定位+修复
大文本常混杂编码(比如日志中某行是 GBK 写入的乱码),用 errors='ignore' 看似省事,实则埋雷:丢数据、后续解析失败、统计偏差。更稳妥的做法是捕获异常,记录位置,再决定跳过或转码。
def robust_line_reader(filepath):
with open(filepath, 'rb') as f: # 先以 bytes 打开
for i, line_bytes in enumerate(f, 1):
try:
line = line_bytes.decode('utf-8').rstrip('\n\r')
yield line
except UnicodeDecodeError:
# 记录哪一行出错,方便人工核查
print(f"Line {i} decode failed: {line_bytes[:50]!r}")
continue
- 别依赖
chardet在循环里实时检测编码——每行都测,性能暴跌,且小样本误判率高 - 如果确定是混合编码,优先用
open(..., encoding='latin-1')(它能解任意 byte,不会报错),再对可疑行单独用bytes.decode('gbk', errors='replace') - Windows 下注意
\r\n和 Unix 的\n差异:line.rstrip('\n\r')比line.strip()更精准,避免误删开头/结尾空格
比生成器还快?试试 mmap + find 定位换行符
当你要随机访问某几行(比如第 100 万行)、或需要极高吞吐(如实时日志 tail),纯 Python 生成器仍有 GIL 和字符串拷贝开销。这时可上 mmap:把文件映射为内存视图,用 find(b'\n') 快速跳转,避免反复系统调用。
import mmap <p>def mmap_line_reader(filepath): with open(filepath, 'rb') as f, mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm: start = 0 while True: end = mm.find(b'\n', start) if end == -1: # 到文件尾 if start </p>
-
mmap不加载全文进 Python 堆,只映射虚拟内存,适合 >10GB 文件 - Windows 上需确保文件打开时没被其他进程独占写入,否则
mmap报PermissionError - 不能用于网络文件或 stdin,只适用于本地普通文件
真正卡住人的,往往不是“怎么 yield”,而是忘了关文件、编码试错了三次才想起来查 BOM、或者在生成器里偷偷做了 list.append() 导致内存悄悄涨起来。流式处理的纪律性,比语法更重要。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










