"聪明的程序员不是算得更快,而是算得更少 。"
------ 迭代器与生成器,正是 Python 践行这一哲学的优雅体现。
问题引入
你有没有遇到过这样的场景?
- 想遍历一个超大文件,但内存爆了 ❌
- 写了个
range(10**9),结果电脑卡死 🐌 - 面试被问:"迭代器和生成器有啥区别?" 瞬间语塞 😅
别慌!今天我们就把这两个看似高冷的概念,用一杯咖啡的时间讲清楚 ☕️。它们不是魔法,而是延迟计算(Lazy Evaluation) 的实用工具------只在你需要时才干活,绝不提前内卷!
核心剖析
迭代器(Iterator):会"一步一步走"的对象
在 Python 中,迭代器 是一个实现了 __iter__() 和 __next__() 方法的对象。它遵循迭代协议:
__iter__()返回自身(支持for循环)__next__()返回下一个值,若无则抛出StopIteration
python
class Countdown:
def __init__(self, start):
self.start = start
def __iter__(self):
return self
def __next__(self):
if self.start <= 0:
raise StopIteration
self.start -= 1
return self.start + 1
# 使用
for num in Countdown(3):
print(num) # 输出: 3, 2, 1
💡 所有可迭代对象(如 list、dict)本身不是迭代器 ,但可以通过
iter()转成迭代器。
生成器(Generator):写起来像函数,用起来像迭代器
生成器 是创建迭代器的"快捷方式"。你只需在函数中用 yield 替代 return,Python 自动帮你实现迭代协议!
python
def countdown_gen(n):
while n > 0:
yield n
n -= 1
# 使用
for num in countdown_gen(3):
print(num) # 输出: 3, 2, 1
关键区别来了👇:
| 特性 | 迭代器 | 生成器 |
|---|---|---|
| 创建方式 | 手写类 + __iter__/__next__ |
函数 + yield |
| 内存占用 | 手动控制 | 自动优化,极低 |
| 可读性 | 较繁琐 | 极简清晰 ✅ |
| 适用场景 | 复杂状态管理 | 流式数据、无限序列 |
🚀 生成器本质是语法糖,但它甜得恰到好处!
动手实践:处理大文件不崩内存
假设你有一个 10GB 的日志文件,想逐行读取并过滤错误信息:
python
def read_large_file(file_path):
"""生成器:按需读取,不加载全文件到内存"""
with open(file_path, 'r') as f:
for line in f:
if 'ERROR' in line:
yield line.strip()
# 使用(即使文件巨大,内存也稳如老狗)
for error_line in read_large_file('app.log'):
print(error_line)
对比暴力做法 lines = open('app.log').readlines() ------ 后者可能直接 OOM(Out of Memory)!
避坑指南 ⚠️
-
生成器只能遍历一次!
pythongen = (x for x in range(3)) list(gen) # [0, 1, 2] list(gen) # [] ← 已耗尽!解决方案:需要多次使用?转成
list,或重新调用生成器函数。 -
别混淆"可迭代对象"和"迭代器"
pythonlst = [1, 2, 3] iter(lst) is iter(lst) # False!每次返回新迭代器 it = iter(lst) iter(it) is it # True!迭代器的 __iter__ 返回自己 -
生成器表达式 vs 列表推导式
python# 列表推导式:立即计算,占内存 squares_list = [x**2 for x in range(1000000)] # 生成器表达式:延迟计算,省内存 squares_gen = (x**2 for x in range(1000000))
延伸思考
-
yield from是什么?它用于"委托"另一个生成器,避免嵌套
for循环。比如合并多个生成器流。 -
生成器能接收外部数据吗?
可以!通过
generator.send(value)实现双向通信,常用于协程(async/await 的底层基础之一)。 -
为什么
range()不是生成器?因为它是可重复迭代的序列对象 ,且支持
len()、索引等操作------生成器做不到这些。
小结 & 行动号召 💡
- 迭代器:协议驱动,手动实现,灵活但啰嗦
- 生成器 :
yield一行搞定,内存友好,开发首选
下次当你需要处理"大量数据""无限序列"或"流式输入"时,记得问自己:
"我能用生成器让它'懒'一点吗?"
试试改写你项目中的某个循环,用生成器替代列表推导式,观察内存变化吧!欢迎在评论区分享你的"懒人优化"成果~ 🧪✨