处理 10GB 文件不爆内存,Python 迭代器凭什么
1. 场景案例:Python大文件处理
去年遇到一个数据清洗任务,日志文件足足有12GB。同事的第一反应很直接:用readlines()把文件全部读进内存,再逐条处理。结果程序跑了三分钟,直接报了个MemoryError。
免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
其实只改了一个地方:把readlines()换成直接遍历文件对象。就这么一个调整,内存峰值从接近14GB降到了不足100MB。
背后的区别很简单:前者是一次性加载,后者则利用了Python迭代器的惰性特性。很多开发者写了多年Python,依然习惯用列表推导式处理一切。不是说不能用,但遇到大数据量时,迭代器和生成器才是那个“正解”。

2. 什么是迭代器
用大白话说,迭代器就是一个知道怎么“一个一个给”东西的对象。你每次问它要下一个,它就给你下一个;给完了,就抛个信号说“没了”,整个过程干净利落。
Python里那个熟悉的for x in something:语法,底层调用的正是迭代器协议。这个协议的核心,就两个方法:
__iter__:返回迭代器对象本身。__next__:返回下一个元素。如果没元素了,就抛出StopIteration异常。

看,这就是迭代器协议的全部内容,两个方法,简单直接。
class Counter:
def __init__(self, max_num):
self.max_num = max_num
self.current = 0
def __iter__(self):
return self
def __next__(self):
if self.current >= self.max_num:
raise StopIteration
self.current += 1
return self.current
for i in Counter(5):
print(i) # 输出:1, 2, 3, 4, 5
上面这个Counter类就是一个自定义迭代器。__iter__返回自己,__next__每次递增并返回当前值,到上限就抛出异常结束迭代。
3. 可迭代对象 vs 迭代器
这两个概念经常被混用,但它们确实不是一回事。
- 可迭代对象:拥有
__iter__()方法,能返回一个迭代器。比如列表(list)、元组(tuple)、字符串(str)、字典(dict)。 - 迭代器:不仅拥有
__iter__()方法,还有__next__()方法,本身就能被遍历。
举个例子,列表是可迭代对象,但它本身不是迭代器。你可以用for循环遍历它,但不能直接对它调用next()。
lst = [1, 2, 3]
next(lst) # TypeError: list object is not an iterator
it = iter(lst) # 通过iter()函数获取它的迭代器
next(it) # 1
next(it) # 2
next(it) # 3
next(it) # 抛出StopIteration异常
这里的iter()函数,其实就是调用了对象内部的__iter__()方法。
简单总结一下:
- 可迭代对象不一定是迭代器。
- 迭代器一定是可迭代对象。
- 可迭代对象可以通过
iter()函数得到一个迭代器。
4. 生成器:迭代器的快捷方式
实现一个完整的迭代器类需要定义两个方法,略显繁琐。而生成器提供了一种更简洁的实现方式,它会自动满足迭代器协议。
创建生成器,最常用的方式就是使用yield关键字。一个函数里只要包含了yield,它就变成了生成器函数。
def counter(max_num):
current = 0
while current < max_num:
current += 1
yield current
for i in counter(5):
print(i) # 输出:1, 2, 3, 4, 5
yield和return的关键区别在于:return执行完函数就彻底结束了,而yield会暂停函数执行,保存当前状态,下次调用next()时再从暂停的地方继续。这正是生成器节省内存的秘诀——它不事先存储所有结果,而是“用一次,算一次”。
(1) 生成器表达式
还有一种更轻量的创建方式:生成器表达式。只需把列表推导式的方括号[]换成圆括号()。
# 列表推导式 - 一次性生成所有结果,全部加载到内存
squares = [x*x for x in range(1000000)] # 占内存约8.06MB
# 生成器表达式 - 惰性计算,用一次算一次
squares = (x*x for x in range(1000000)) # 内存占用极小,约200字节


当数据量达到一定规模时,这两种方式的区别就不再是“快一点”或“慢一点”,而是“能跑”和“根本跑不了”的天壤之别。
5. 实战场景
(1) 场景一:大文件处理
# 错误写法:一次性加载
with open('huge.log') as f:
lines = f.readlines() # 返回list,全部加载到内存
for line in lines:
process(line)
# 正确写法:利用文件对象自身的迭代器特性
with open('huge.log') as f:
for line in f: # 文件对象是迭代器,每次只读一行
process(line)
Python的文件对象本身就是一个迭代器,直接遍历它,解释器会自动按行惰性读取,避免内存爆炸。
(2) 场景二:数据流处理
def read_api_pages(url):
page = 1
while True:
response = requests.get(url, params={'page': page})
data = response.json()
if not data:
break
yield from data # 将子迭代器的值直接产出
page += 1
for item in read_api_pages('https://api.example.com/items'):
process(item)
这里用到了yield from,它能将子迭代器的值直接传递出来。这种模式特别适合处理分页API、数据库游标等需要连续获取数据的场景。
(3) 场景三:管道式数据处理
def read_csv(filepath):
with open(filepath) as f:
for line in f:
yield line.strip().split(',')
def filter_age(records, min_age):
for record in records:
if int(record[2]) >= min_age:
yield record
def format_output(records):
for record in records:
yield f"{record[0]}|{record[1]}|{record[2]}"
# 像组装流水线一样组合起来
data = read_csv('users.csv')
filtered = filter_age(data, 18)
formatted = format_output(filtered)
for line in formatted:
print(line)
每个函数都是一个生成器,数据像在流水线(pipeline)中传递。中间不需要任何额外的列表或容器来存储临时结果,内存效率极高。
6. 常见坑
第一个坑:迭代器和生成器是“一次性消耗品”。
gen = (x for x in range(5))
list(gen) # 第一次转换,得到 [0, 1, 2, 3, 4]
list(gen) # 第二次转换,得到 [] —— 因为生成器已经耗尽了
遍历完一次后,迭代器或生成器就空了。如果需要重复使用,要么将其转换为列表,要么重新创建。
第二个坑:生成器函数返回的是生成器对象,不是执行结果。
def my_gen():
yield 1
yield 2
yield 3
result = my_gen() # 这里只是得到了一个生成器对象,函数体内的代码并未执行
# 需要遍历(如用for循环或next())才会触发yield语句执行
新手常会困惑:明明调用了函数,为什么没有输出?记住,调用生成器函数返回的是一个待“激活”的生成器对象,真正的计算发生在迭代时。
7. 总结
最后,我们来梳理一下这几个核心概念的关系:
- 可迭代对象 (Iterable):有
__iter__()方法。 - 迭代器 (Iterator):继承自可迭代对象,多了
__next__()方法。__iter__()通常返回self,并能保持迭代状态。文件对象就在这里。 - 生成器 (Generator):一种特殊的迭代器,由生成器函数或生成器表达式创建。除了迭代器的方法,还有
send(),throw(),close()等方法,使用yield关键字实现。
归根结底,迭代器和生成器的核心优势在于惰性:惰性访问、惰性计算,用一次算一次,对内存极其友好。它们的协议也足够简单,实现__iter__和__next__两个方法就能搞定。生成器则是更便捷的语法糖,yield让一个普通函数变身迭代器。
处理小数据时,列表和生成器的差异或许微不足道。可一旦数据量上来,这其中的区别,往往就决定了程序是顺利运行,还是中途崩溃。
文章开头那个12GB的日志文件案例,后来成了技术面试中的一个经典问题。题目本身不难,但能清晰地分辨出一个开发者,是否真正理解了Python迭代器与生成器的原理,以及能否在关键时刻做出正确的应用选择。
相关攻略
Python连接MySQL驱动安装指南:直接装mysql-connector-python,别用mysql-connector 在Python项目中配置MySQL数据库连接,第一步选择正确的驱动包至关重要。许多开发者首次尝试时就会遇到安装失败或连接报错的问题。请牢记这个核心解决方案:务必直接安装 m
1 场景案例:Python大文件处理 去年遇到一个数据清洗任务,日志文件足足有12GB。同事的第一反应很直接:用readlines()把文件全部读进内存,再逐条处理。结果程序跑了三分钟,直接报了个MemoryError。 其实只改了一个地方:把readlines()换成直接遍历文件对象。就这么一个
01 准备工作 使用Python程序自动发送邮件的第一步,是完成邮箱的客户端授权配置。这个过程并不复杂,核心在于开启SMTP POP3服务并获取一个专用的授权码。 首先,登录您的邮箱(例如QQ邮箱、163邮箱或126邮箱),进入“设置”或“账户”管理页面,找到“POP3 SMTP服务”或类似选项并将
Python 3与Python 2:那些你必须了解的关键演变 在软件开发、数据科学以及人工智能等前沿领域,Python语言占据着举足轻重的地位。伴随着语言的持续演进,从Python 2升级到Python 3是一次里程碑式的重大变革。尽管两者在语法上存在继承关系,但诸多核心差异深刻影响着代码的兼容性与
Pywinrm:打通跨平台管理的最后一公里 在混合IT环境里,Linux机器想管Windows服务器,这事儿一直挺让人头疼的。你猜怎么着?当SSH够不着Windows,PowerShell Remoting又没个统一的Python接口时,pywinrm模块就登场了。它通过Windows远程管理(Wi
热门专题
热门推荐
云顶之弈S17星神赛季:重装妖姬阵容深度解析与上分攻略 云顶之弈S17“星神”赛季现已全面开启,全新羁绊、英雄与赛季机制为战场带来了颠覆性的变化。在众多阵容套路中,一套以“诡术妖姬”乐芙兰为主C,搭配重装战士与法官羁绊的体系表现尤为亮眼,成为当前版本稳定上分的强力选择。本文将为您深度解析这套重装妖姬
微软1月更新KB5074109新BUG:云存储文件导致OneDrive等应用卡死 近日,微软Windows用户遭遇了一个普遍困扰。1月20日,微软在其官方Windows发布健康仪表板上更新了状态,正式确认了1月累积更新KB5074109中存在一个影响广泛的缺陷。该问题波及了从Windows 10到W
在Linux系统管理中,Systemctl被誉为服务管理的“全能指挥官”。无论是启动核心服务、监控运行状态,还是进行系统故障排查,它都是管理员必备的利器。本文将深入解析如何利用Systemctl命令全面查看系统所有服务,并掌握高效管理技巧。 什么是 Systemctl? Systemctl是syst
苹果macOS 26 4开发者预览版 Beta 2发布:修复窗口缩小指针不跟随问题 苹果公司如期发布了面向Mac用户的macOS 26 4第二个开发者预览版(Beta 2),内部版本号为25E5218f。此次更新距离上一个Beta RC版本发布正好一周,延续了苹果系统更新的稳定节奏。 如何升级 iO
《亿万光年》:从舰船养成到战场微操,一份深度编队指南 在《亿万光年》的浩瀚星海中,想要成为一位合格的星际指挥官,核心秘诀无外乎两点:扎实的舰船养成与灵活的编队搭配,再辅以关键时刻的战场微操。这套组合拳,是应对宇宙中各种复杂战局的不二法门。今天,我们就来深入拆解这套玩法体系,助你打造一支所向披靡的无敌





