前言
Python 的迭代器和生成器常被拿来面试考,也常出现在高效处理大数据的场景。可一提到这些概念,很多同学脑袋就大了——今天咱们换个思路,用超市买零食的生活场景,把这些看似“高级”的知识点讲清楚!
1. 用零食理解核心概念
- 一次性全搬走(如把整箱零食一口气搬回家):像列表、字典等可迭代对象,所有数据一次性都加载到内存里,占地方。
- 吃完再拿下一包(每次只取一包,吃完再拿):就像迭代器和生成器的“懒加载”,用多少取多少,省力又省空间!
一句话总结:
- 迭代器:自己造了个“取零食”的机器,要手动实现“怎么取”。
- 生成器:用“yield”关键字,像买了现成的自动取零食机,更省心。
2. 迭代器(Iterator)
2.1 概念与特点
-
惰性取值:只在需要的时候才取出一个元素,不提前加载所有内容。
-
实现协议:
__iter__():返回自身。__next__():返回下一个元素,没有则抛出StopIteration异常。
2.2 创建迭代器实例
假如你有 4 包饼干,想每次只拿一包:
snacks = ["奥利奥", "薯片", "巧克力棒", "饼干"]
snack_iter = iter(snacks)
print(next(snack_iter)) # 奥利奥
print(next(snack_iter)) # 薯片
print(next(snack_iter)) # 巧克力棒
print(next(snack_iter)) # 饼干
2.3 自定义迭代器
自己做一个“抽奖箱”迭代器,每次抽出一个奖品:
class LuckyBox:
def __init__(self, gifts):
self.gifts = gifts
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index < len(self.gifts):
gift = self.gifts[self.index]
self.index += 1
return gift
else:
raise StopIteration
lucky_iter = LuckyBox(["玩偶", "水杯", "U盘"])
for gift in lucky_iter:
print("抽到了:", gift)
3. 可迭代对象 vs 迭代器
- 可迭代对象(如列表、字符串、range)只实现了
__iter__(),不能直接用next()。 - 迭代器同时实现了
__iter__()和__next__(),能直接用next()取数据。
快速判断方法:
my_list = ["薯片", "可乐"]
print(hasattr(my_list, "__iter__")) # True
print(hasattr(my_list, "__next__")) # False
my_iter = iter(my_list)
print(hasattr(my_iter, "__iter__")) # True
print(hasattr(my_iter, "__next__")) # True
print(next(my_iter)) # 薯片
4. 生成器(Generator)
4.1 概念与特点
- 用 yield 编写:省去了手动管理状态。
- 支持惰性计算:每次 next() 生成一个数据,随用随取。
4.2 生成器函数实例
模拟“分批领取快递”,每次通知取一件:
def express_pickup():
print("通知领取快递:第1件")
yield "快递1"
print("通知领取快递:第2件")
yield "快递2"
print("通知领取快递:第3件")
yield "快递3"
pickup = express_pickup()
print(next(pickup)) # 通知领取快递:第1件 快递1
print(next(pickup)) # 通知领取快递:第2件 快递2
print(next(pickup)) # 通知领取快递:第3件 快递3
# print(next(pickup)) # StopIteration(没有快递了)
4.3 生成器表达式
用小括号生成懒加载数据(如:自动生成桌号)
table_numbers = (f"桌号-{i}" for i in range(1, 6))
print(next(table_numbers)) # 桌号-1
print(next(table_numbers)) # 桌号-2
for table in table_numbers:
print(table) # 桌号-3,桌号-4,桌号-5
4.4 大数据应用举例
模拟一行一行处理超大日志文件:
def read_log(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
for line in f:
yield line.strip()
# for log in read_log("mylog.txt"):
# print(log)
5. 练习:生成器版斐波那契数列
取前10个“成长值”:
def fibonacci(n):
a, b = 0, 1
for _ in range(n):
yield b
a, b = b, a + b
for num in fibonacci(10):
print(num, end=" ")
# 输出:1 1 2 3 5 8 13 21 34 55
6. 迭代器 vs 生成器 总结对比
| 特性 | 迭代器 | 生成器 |
|---|---|---|
| 实现方式 | 类定义,手动实现方法 | 函数 + yield,自动实现 |
| 代码简洁性 | 代码较复杂 | 非常简单 |
| 状态管理 | 需手动维护索引等状态 | 自动保存函数执行位置 |
| 惰性计算 | 支持 | 支持 |
| 适合场景 | 复杂遍历、定制迭代逻辑 | 简单数据流、一次性生成流程 |
7. 必知细节与易错点
同一个生成器实例通常只消费一次。若日志预览先把它遍历完,再交给统计函数,后者可能收到空数据。需要重复读取时,应重新创建生成器,或在确认内存可接受后物化;这不是给对象再套一个 iter() 就能重置的。
-
迭代器和生成器都只能往前,不能回退
被 next() 或 for 循环取过的元素,无法回头再取,生成器只能遍历一次。
nums = iter([10, 20, 30]) for n in nums: print(n) # 10 20 30 for n in nums: print(n) # 什么都不输出(已经耗尽) -
生成器可以动态接收外部值(send)
send()能给生成器传递数据,实现协程效果。def echo(): received = yield "hello" yield f"got:{received}" g = echo() print(next(g)) # hello print(g.send("test")) # got:test -
判断对象是迭代器还是可迭代对象
推荐用 collections.abc 判断:
from collections.abc import Iterator, Iterable print(isinstance([], Iterator)) # False print(isinstance([], Iterable)) # True print(isinstance(iter([]), Iterator)) # True -
常见支持迭代协议的数据类型
- 所有内置集合类型(list、tuple、set、dict、str)
- 文件对象(
for line in open("file.txt")本质就是迭代器) - 自定义实现了
__iter__或__next__的类
-
for 循环的本质
for x in obj:实际做的是:先iter(obj),然后每次next(),直到 StopIteration。 -
常见易错点与坑
- next() 记得处理 StopIteration,for 循环最安全。
- 生成器表达式只能用一次,不能回头。多个变量会“用光”生成器:
g = (x for x in range(3)) a = list(g) b = list(g) print(a, b) # [0, 1, 2] [] -
生成器也能 return(用来抛 StopIteration)
在生成器里 return,会触发 StopIteration,并可带返回值(少用,偶有面试考)。
def foo(): yield 1 return 100 g = foo() try: print(next(g)) print(next(g)) except StopIteration as e: print("生成器return的值:", e.value) # 生成器return的值:100 -
itertools 标准库
itertools 提供强大的“流式数据处理”工具,推荐配合生成器、迭代器处理大数据、组合、分组、切片等。
import itertools for x in itertools.islice(range(100), 10, 20): print(x) # 输出10~19 -
Python 3.x 推荐用法
- dict.keys()、dict.values()、dict.items() 返回可迭代视图对象(不是 list,不是迭代器),可以 for 遍历,也能 iter()。
- 注意和 Python 2.x 的区别。
-
实际开发建议
- 处理大数据优先用生成器、迭代器,节省内存。
- 只需遍历一次用生成器;需多次遍历、查找,转 list。
- 组合式写法可读性优先,别滥用一行流。
8. 一句话总结
迭代器和生成器的精髓就是“懒加载”——数据不用一次性拿完,只在需要时才生成,用于大数据、流式处理、节省内存都非常高效。
建议: 需要简单的数据流,用生成器;遇到更复杂或需要自定义取数规则,再手写迭代器!
觉得有用欢迎点赞收藏,关注 @Zack-Zhang1031 持续更新!
💬 评论
这套方案在哪一步卡住了?也欢迎补充你的实现方式或踩坑经验。