news 2026/3/27 0:17:54

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/3/25 18:49:16

mptools v8.0配置文件解析:系统学习与实践应用

深入理解 mptools v8.0 配置系统&#xff1a;从结构到实战的完整指南在现代工程实践中&#xff0c;自动化运维工具早已不再是“可有可无”的附加组件&#xff0c;而是支撑高效交付、稳定运行的核心基础设施。面对日益复杂的部署环境和多变的操作需求&#xff0c;如何通过一份配…

作者头像 李华
网站建设 2026/3/25 8:22:08

ResNet18应用案例:农业病虫害识别系统

ResNet18应用案例&#xff1a;农业病虫害识别系统 1. 引言&#xff1a;从通用物体识别到农业场景落地 在人工智能赋能千行百业的今天&#xff0c;深度学习模型正逐步从实验室走向田间地头。ResNet18作为经典的轻量级卷积神经网络&#xff0c;在ImageNet等大规模数据集上展现了…

作者头像 李华
网站建设 2026/3/26 7:33:05

有源蜂鸣器PWM调音控制:超详细版实现指南

用PWM玩转有源蜂鸣器&#xff1a;不只是“滴”一声那么简单你有没有遇到过这样的场景&#xff1f;按下设备按键&#xff0c;只听到千篇一律的“滴”声&#xff1b;报警触发时&#xff0c;声音单调得像老式电话忙音——毫无辨识度。在今天这个追求极致交互体验的时代&#xff0c…

作者头像 李华
网站建设 2026/3/19 16:21:37

RISC为何高效?以ARM为例核心要点

RISC为何高效&#xff1f;从ARM的设计哲学看现代处理器的能效革命你有没有想过&#xff0c;为什么你的手机可以连续播放十几个小时视频而不发烫&#xff0c;而一台高性能笔记本在跑大型软件时却风扇狂转、掌心滚烫&#xff1f;这背后的核心差异&#xff0c;并不完全在于电池大小…

作者头像 李华
网站建设 2026/3/26 17:34:52

ResNet18优化实战:模型量化压缩技巧

ResNet18优化实战&#xff1a;模型量化压缩技巧 1. 背景与挑战&#xff1a;通用物体识别中的效率瓶颈 在当前AI应用广泛落地的背景下&#xff0c;通用物体识别已成为智能设备、边缘计算和Web服务的核心能力之一。基于ImageNet预训练的ResNet-18模型因其结构简洁、精度适中、参…

作者头像 李华
网站建设 2026/3/25 9:16:23

ResNet18技术解析:残差块设计精要

ResNet18技术解析&#xff1a;残差块设计精要 1. 引言&#xff1a;通用物体识别中的ResNet-18 在现代计算机视觉系统中&#xff0c;通用物体识别是构建智能感知能力的核心任务之一。从自动驾驶中的环境理解到智能家居的场景感知&#xff0c;模型需要具备对上千类常见物体和复…

作者头像 李华