在Python里处理超出物理内存的数据集时,最直接的表现就是解释器抛出MemoryError,程序被迫中断。根本原因在于Python对象以及底层C扩展(如NumPy数组、pandas的DataFrame)都需要在堆上分配连续或近似连续的内存空间,当单次分配请求无法被满足,或者总驻留内存超过系统可用量,操作系统就会拒绝分配并抛出异常。分块处理策略通过把数据拆成可管理的小片段,让任意时刻内存中只保留一个片段以及相关的中间状态,从而把峰值内存压到可控范围。

为什么一次性加载会触发MemoryError
以pandas读取CSV为例,调用pd.read_csv(path)时,库会先扫描文件推断类型,然后在内存中构建列存储结构。每一列背后是NumPy的ndarray,字符串列还可能使用Python对象数组,实际占用往往是原始文件体积的二到五倍。假设机器只有8GB内存,而文件解压后仅数据就占20GB,显然任何一步分配都会失败。
另一个容易被忽略的点是,Python的垃圾回收并不保证立刻把释放的内存交还操作系统。即使你del掉一个大DataFrame,进程常驻内存(RSS)也可能不降。因此靠“读一点删一点”的侥幸思路往往撑不过几次迭代,只有从源头控制单次载入量才可靠。
基于pandas的chunksize分块
pandas自带的read_csv支持chunksize参数,返回一个TextFileReader迭代器,每次yield一个最多含chunksize行的DataFrame。这种方式最简单,也保留了类型推断、解析等能力。
import pandas as pd
file_path = "ipipp.com_data/large.csv"
chunk_size = 100_000
total_sum = 0
max_value = float("-inf")
# 使用chunksize分块读取,避免一次性载入
reader = pd.read_csv(file_path, chunksize=chunk_size)
for idx, chunk in enumerate(reader):
# 假设有一列叫 amount
total_sum += chunk["amount"].sum()
current_max = chunk["amount"].max()
if current_max > max_value:
max_value = current_max
print(f"已处理第 {idx} 块,累计求和 {total_sum}")
print(f"全局求和: {total_sum}, 最大值: {max_value}")
上面的代码每次只在内存中保留十万行数据,无论源文件多大,峰值内存基本稳定。对于需要分组聚合的场景,可以在每块内部先groupby,再把各块的中间结果合并,而不是试图构建全量groupby。
chunksize方案的优点是与pandas生态无缝衔接,缺点是无法精确控制字节级内存,因为不同类型列的内存放大效应不同。如果单块仍超内存,可继续调小chunksize,或者只挑选必要列(usecols)降低每块的宽度。
手动按文件指针切分
当数据不是规则表格,或希望更细粒度控制内存时,可以用Python内置文件对象逐行读取,自行累积到一定数量再处理。这样连pandas的解析开销都可省去。
def process_line(line):
# 简单按逗号拆分并转数值,实际可替换为复杂逻辑
parts = line.strip().split(",")
return float(parts[1])
def chunked_file_reader(path, chunk_lines=50000):
buffer = []
with open(path, "r", encoding="utf-8") as f:
# 跳过表头
next(f)
for line in f:
buffer.append(process_line(line))
if len(buffer) >= chunk_lines:
yield buffer
buffer = []
if buffer:
yield buffer
for chunk in chunked_file_reader("ipipp.com_data/large.log"):
avg = sum(chunk) / len(chunk)
print(f"当前块平均: {avg}")
这种写法把内存占用压到仅由chunk_lines和单行长度决定,非常适合日志、JSON Lines等半结构化文本。若配合生成器表达式,还能实现多级管道:读块、清洗块、写块完全流式化。
需要注意,手动切分可能把一个逻辑记录截断在多行(如多行JSON),此时应维护状态机而非死板按行数切割。另外,在Windows上打开大文件建议加newline=""以避免换行符转换带来的额外拷贝。
分块写出与最终合并
处理完每块后通常需要落地结果。若结果是独立文件,直接append模式写即可;若是全局排序或全量去重,则分块本身无法完成,需要外部归并或借助数据库。
| 策略 | 适用场景 | 内存特征 |
|---|---|---|
| chunksize+聚合 | 求和、最值、分组统计 | 恒定低占用 |
| 手动指针切分 | 非表格文本、极致控内存 | 极小占用 |
| 分块写中间文件+归并 | 全局排序、去重 | 磁盘换内存 |
对于必须全局排序的情况,可先分块排序写出多个小文件,再用heapq.merge做流式归并,整个过程内存只保留每个文件的当前头元素。这种思想与Unix的sort -m一致,在Python中也能轻松实现。
最后提醒,分块处理虽能化解MemoryError,但会牺牲部分向量化性能。若机器内存其实够用,优先用pandas整体操作更快;只有当确证内存不足时,再引入分块,并在块大小上做基准测试,找到吞吐与内存的平衡点。
PythonMemoryErrorchunk_processing修改时间:2026-08-06 17:12:32