news 2026/4/18 7:37:38

Day 20:【99天精通Python】迭代器与生成器 - 内存优化的黑科技

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Day 20:【99天精通Python】迭代器与生成器 - 内存优化的黑科技

Day 20:【99天精通Python】迭代器与生成器 - 内存优化的黑科技

前言

欢迎来到第20天!

在处理数据时,我们经常会遇到这样的场景:需要处理一个几 GB 甚至几 TB 的大文件,或者需要生成一个包含 1 亿个数字的列表。
如果直接把所有数据一次性加载到内存中(比如用列表),电脑内存瞬间就会爆满,程序卡死。

Python 给我们提供了一套优雅的解决方案:迭代器 (Iterator)生成器 (Generator)。它们的核心思想是:“用一个,拿一个”,而不是**“一次性全拿出来”**。这就像是流水线作业,既节省内存,又提高了效率。

本节内容:

  • 可迭代对象 (Iterable) vs 迭代器 (Iterator)
  • for循环的本质
  • yield关键字详解
  • 生成器函数与生成器表达式
  • 实战:斐波那契数列与大文件读取

一、迭代器:按需获取

1.1 什么是可迭代对象 (Iterable)?

简单说,凡是能用for循环遍历的东西,都是可迭代对象。
例如:列表、元组、字符串、字典、集合。

fromcollections.abcimportIterableprint(isinstance([],Iterable))# Trueprint(isinstance("abc",Iterable))# Trueprint(isinstance(100,Iterable))# False (整数不可迭代)

1.2 什么是迭代器 (Iterator)?

迭代器是一个"更高级"的对象,它不仅可以被遍历,还记录了当前访问到了哪里
它必须实现两个方法:

  1. __iter__(): 返回迭代器对象本身。
  2. __next__(): 返回下一个元素。如果没有元素了,抛出StopIteration异常。

1.3 iter() 和 next()

我们可以用iter()函数把一个列表变成迭代器。

nums=[1,2,3]it=iter(nums)# 创建迭代器print(next(it))# 1print(next(it))# 2print(next(it))# 3# print(next(it)) # 报错: StopIteration

1.4 for 循环的本质

其实for循环内部就是在做这件事:

  1. 调用iter(obj)获取迭代器。
  2. 不断调用next(it)获取元素。
  3. 遇到StopIteration异常时,停止循环。

二、生成器 (Generator):最简单的迭代器

手动写一个迭代器类太麻烦了(需要写__iter____next__)。Python 提供了生成器,让你用写函数的语法来实现迭代器。

2.1 yield 关键字

yield的作用类似于return,但它不会结束函数,而是暂停函数,并保存当前的状态。下次调用next()时,函数会从暂停的地方继续执行。

defmy_generator():print("开始生成...")yield1print("暂停回来,继续生成...")yield2print("最后一次...")yield3gen=my_generator()# 此时函数并没有执行!只有调用 next() 才会动。print(next(gen))# 输出:# 开始生成...# 1print(next(gen))# 输出:# 暂停回来,继续生成...# 2

2.2 生成器表达式

类似于列表推导式,只是把方括号[]换成了圆括号()

# 列表推导式 (立即生成所有数据,占内存)list_data=[x**2forxinrange(10)]print(list_data)# [0, 1, 4, ..., 81]# 生成器表达式 (不生成数据,只保存算法,省内存)gen_data=(x**2forxinrange(10))print(gen_data)# <generator object ...># 必须遍历才能取值fornumingen_data:print(num,end=" ")

三、生成器 vs 列表:性能对比

假设我们要处理 1000 万个数字。

importsys# 方式1:列表# 瞬间占用大量内存big_list=[xforxinrange(10000000)]print(f"列表占用内存:{sys.getsizeof(big_list)/1024/1024:.2f}MB")# 结果约 380 MB# 方式2:生成器# 几乎不占内存big_gen=(xforxinrange(10000000))print(f"生成器占用内存:{sys.getsizeof(big_gen)}Bytes")# 结果只有 100多 Bytes (无论数据多大,它都只占这么多)

结论:处理海量数据时,必须使用生成器。


四、实战练习

练习1:斐波那契数列生成器

斐波那契数列:1, 1, 2, 3, 5, 8, 13…
如果用递归算第100位会卡死,用生成器则轻松秒杀。

deffib_generator(n):"""生成前 n 个斐波那契数"""a,b=0,1count=0whilecount<n:yieldb# 产出当前数值a,b=b,a+b count+=1# 打印前10个fornuminfib_generator(10):print(num,end=" ")# 1 1 2 3 5 8 13 21 34 55

练习2:大文件读取器

假设有一个 10GB 的日志文件,每行一条日志。我们需要查找包含 “ERROR” 的行。
直接readlines()会内存溢出,我们用生成器逐行读取。

defread_large_file(filename):"""生成器:每次只读一行"""withopen(filename,"r",encoding="utf-8")asf:forlineinf:yieldline# 暂停,把行给出去# 使用# 假设 log.txt 存在# for line in read_large_file("log.txt"):# if "ERROR" in line:# print(f"发现错误: {line.strip()}")

:其实 Python 的文件对象本身就是可迭代的,直接for line in f效果也是一样的,这里只是为了演示原理。


五、深入理解:send() 方法

生成器不仅可以产出数据 (yieldout),还可以接收数据 (yieldin)。

defeater():print("准备吃饭...")whileTrue:food=yield# 接收外部传进来的值print(f"吃了{food}")e=eater()next(e)# 预激生成器 (执行到第一个 yield 并暂停)e.send("包子")# 吃了 包子e.send("面条")# 吃了 面条e.close()# 关闭生成器

这是协程 (Coroutine)的雏形,也是 Python 异步编程 (async/await) 的基石。


六、常见问题

Q1:生成器能遍历第二次吗?

不能。生成器是"一次性"的。遍历完一遍后,指针就在最后了,再调用next()会抛错。如果需要再次遍历,必须重新创建生成器对象。

Q2:return在生成器里起什么作用?

在生成器函数中,return等同于抛出StopIteration异常,用于终止生成。


七、小结

迭代 Iteration

Iterable (可迭代对象)

Iterator (迭代器)

列表, 字符串...

实现了iter

实现了iternext

next() 取值

只能往前,不能回头

Generator (生成器)

函数中包含 yield

生成器表达式 (x for x in ...)

省内存,惰性计算

关键要点

  1. Iterable是原材料,Iterator是流水线。
  2. 生成器是最简单的迭代器写法。
  3. yield是暂停键,next是播放键。
  4. 处理大量数据时,优先考虑生成器。

八、课后作业

  1. 自定义 range:编写一个生成器函数my_range(start, end, step),模仿 Python 内置range的功能(支持浮点数步长)。
  2. 素数生成器:编写一个生成器,无限生成素数(2, 3, 5, 7…)。并在外部循环中打印前 20 个素数。
  3. 日志清洗:模拟一个包含脏数据的列表data = ["info: ok", "error: fail", None, "", "warn: check"]。编写一个生成器,清洗掉None和空字符串,并统一转换为大写。

下节预告

Day 21:基础篇总结与综合实战- 恭喜!你已经完成了 Python 基础篇的所有核心内容。明天我们将通过一个综合性的图书管理系统项目,把这20天的知识串联起来!


系列导航

  • 上一篇:Day 19 - 装饰器
  • 下一篇:Day 21 - 基础篇总结与实战(待更新)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 1:23:41

ResNet18性能对比:ResNet18 vs ResNet50实测

ResNet18性能对比&#xff1a;ResNet18 vs ResNet50实测 1. 引言&#xff1a;为何进行ResNet18与ResNet50的实测对比&#xff1f; 在深度学习图像分类任务中&#xff0c;ResNet&#xff08;残差网络&#xff09; 系列模型因其出色的性能和稳定的训练表现&#xff0c;成为工业…

作者头像 李华
网站建设 2026/4/18 6:57:32

项目应用中Vivado 2023.1多用户License管理策略

Vivado 2023.1多用户License管理实战&#xff1a;如何让有限授权支撑整个FPGA团队高效运转&#xff1f; 在一次跨部门FPGA联合开发项目中&#xff0c;我们团队遭遇了这样一个典型场景&#xff1a;早上9点刚过&#xff0c;三位工程师几乎同时点击“Run Implementation”——布局…

作者头像 李华
网站建设 2026/4/8 23:22:27

多层工业控制板中走线宽度与载流优化策略

走线宽度与载流能力&#xff1a;工业控制板设计中的“看不见的保险丝”你有没有遇到过这样的情况&#xff1f;一块PLC主板在实验室测试时一切正常&#xff0c;可一旦部署到现场满负荷运行几小时后&#xff0c;突然无故重启——排查半天发现&#xff0c;不是软件崩溃&#xff0c…

作者头像 李华
网站建设 2026/4/15 13:17:41

ResNet18技术解析:多类别分类任务实现方法

ResNet18技术解析&#xff1a;多类别分类任务实现方法 1. 引言&#xff1a;通用物体识别中的ResNet18价值 在计算机视觉领域&#xff0c;图像分类是基础且关键的任务之一。随着深度学习的发展&#xff0c;卷积神经网络&#xff08;CNN&#xff09;不断演进&#xff0c;从早期…

作者头像 李华
网站建设 2026/3/29 5:43:47

ResNet18实战:智能监控系统中的物体识别部署

ResNet18实战&#xff1a;智能监控系统中的物体识别部署 1. 引言&#xff1a;通用物体识别的工程价值与ResNet-18的角色 在智能监控、安防预警、行为分析等AIoT场景中&#xff0c;通用物体识别是构建视觉理解能力的基础环节。传统方案依赖云服务API或定制化模型&#xff0c;存…

作者头像 李华
网站建设 2026/4/18 6:03:27

一文说清硬件电路中的LDO设计要点

LDO设计的“坑”与“道”&#xff1a;从选型到热管理&#xff0c;一文讲透硬件电路中的关键细节在嵌入式系统和高精度电子设备的设计中&#xff0c;电源往往决定成败。而在这条“看不见”的电力通路末端&#xff0c;低压差线性稳压器&#xff08;LDO&#xff09;常常扮演着“守…

作者头像 李华