Python3 迭代器与生成器(建议收藏)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观
在 Python3 开发中,迭代器与生成器是处理序列化数据的核心工具。它们如同程序中的“数据流水线”,既能高效管理内存资源,又能灵活控制数据流的生成逻辑。无论是遍历列表、处理无限序列,还是优化内存密集型任务,掌握这两个概念都将显著提升代码的性能与可读性。本文将从基础概念出发,结合生动的比喻和代码示例,帮助读者逐步理解迭代器与生成器的实现原理及应用场景。
一、迭代器(Iterator):数据流的“导航仪”
1.1 什么是可迭代对象?
在 Python 中,任何可以被遍历的对象(如列表、元组、字符串)都称为可迭代对象(Iterable)。它们内部实现了 __iter__()
方法,该方法会返回一个迭代器。例如:
numbers = [1, 2, 3]
iterator = iter(numbers) # 调用 __iter__() 方法,获取迭代器对象
print(next(iterator)) # 输出 1
这里,iter()
函数将列表 numbers
转换为迭代器,next()
函数则逐个取出元素。
1.2 迭代器协议:双方法规则
迭代器必须实现两个核心方法:
__iter__()
: 返回迭代器对象本身(通常返回self
)。__next__()
: 返回序列中的下一个元素,若无元素则抛出StopIteration
异常。
比喻:
想象一个图书馆的目录索引,__iter__()
是打开目录的动作,而 __next__()
是逐页翻阅书名的过程。目录一旦被打开,就能按顺序访问所有书籍,直到最后一页结束。
1.3 手动实现迭代器
通过自定义类,我们可以创建一个简单的迭代器:
class CounterIterator:
def __init__(self, stop):
self.current = 0
self.stop = stop
def __iter__(self):
return self
def __next__(self):
if self.current < self.stop:
value = self.current
self.current += 1
return value
else:
raise StopIteration
counter = CounterIterator(3)
for num in counter:
print(num) # 输出 0, 1, 2
此例中,CounterIterator
类实现了迭代器协议,可生成从 0
到 stop-1
的整数序列。
二、生成器(Generator):代码的“智能流水线”
2.1 生成器函数:用 yield
简化迭代逻辑
生成器是一种特殊的迭代器,其核心是 yield
关键字。与 return
不同,yield
可以暂停函数执行并返回值,待下次调用时继续执行。例如:
def simple_generator():
yield 1
yield 2
yield 3
gen = simple_generator()
print(next(gen)) # 输出 1
print(next(gen)) # 输出 2
每次调用 next(gen)
会恢复函数执行,直到遇到下一个 yield
或函数结束。
2.2 生成器表达式:简洁的数据流构建
生成器表达式类似于列表推导式,但用圆括号包裹,返回生成器对象:
even_numbers = (x for x in range(10) if x % 2 == 0)
for num in even_numbers:
print(num) # 输出 0, 2, 4, 6, 8
与列表推导式相比,生成器表达式按需生成元素,节省内存。
2.3 惰性求值:生成器的性能优势
生成器通过惰性求值(Lazy Evaluation),仅在需要时计算下一个值。例如,生成斐波那契数列时:
def fibonacci(n):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
fib = fibonacci(5)
print(list(fib)) # 输出 [0, 1, 1, 2, 3]
若用列表存储所有斐波那契数,当 n
极大时(如 1000000),内存消耗将显著增加,而生成器能逐个生成并释放临时变量。
三、迭代器与生成器的对比
特性 | 迭代器 | 生成器 |
---|---|---|
实现方式 | 需定义 __iter__() 和 __next__() | 通过 yield 关键字简化实现 |
内存占用 | 需预先存储所有数据 | 按需生成,内存友好 |
复杂度 | 手动管理状态变量 | 状态自动保存,代码更简洁 |
适用场景 | 自定义复杂迭代逻辑 | 需要惰性求值或处理无限序列时 |
比喻:
迭代器如同传统生产线——需要提前规划所有步骤;而生成器则像智能流水线,根据需求动态调整生产节奏,无需囤积半成品。
四、实战案例:用生成器优化内存密集型任务
4.1 处理大文件的逐行读取
假设需要读取一个 1GB 的日志文件,逐行统计关键词出现次数。使用生成器可避免一次性加载整个文件:
def read_large_file(file_path):
with open(file_path, 'r') as file:
for line in file:
yield line.strip()
log_lines = read_large_file('large_log.txt')
for line in log_lines:
# 执行分析逻辑
pass
此方法仅在循环时逐行读取文件,内存占用接近 O(1)
。
4.2 生成无限序列:自然数流
def infinite_counter():
num = 0
while True:
yield num
num += 1
counter = infinite_counter()
for _ in range(5):
print(next(counter)) # 输出 0, 1, 2, 3, 4
此生成器可无限生成自然数,适合需要循环计数的场景(如模拟时钟)。
五、进阶技巧:生成器的 send()
与异常处理
5.1 send()
方法:向生成器传递数据
通过 send()
可在生成器内部恢复执行时传入值,常用于协程(Coroutine)设计:
def calculator():
while True:
expr = (yield) # 接收外部传入的表达式
try:
result = eval(expr)
yield result
except Exception as e:
yield str(e)
calc = calculator()
next(calc) # 初始化生成器
print(calc.send("2 + 3")) # 输出 5
print(calc.send("a / 0")) # 输出 division by zero
此处 send()
允许外部动态修改生成器的计算逻辑。
5.2 异常处理:优雅终止或重置
在迭代过程中,可通过抛出 StopIteration
或捕获异常来控制流程:
def robust_generator():
try:
while True:
value = yield
if value is None:
raise ValueError("Invalid input")
yield value * 2
except GeneratorExit:
print("Generator closed gracefully")
gen = robust_generator()
next(gen) # 初始化
print(gen.send(5)) # 输出 10
gen.close() # 触发 GeneratorExit 异常
结论
迭代器与生成器是 Python3 中处理序列化数据的基石。迭代器通过严格的协议规范,提供了底层的控制能力;而生成器借助 yield
关键字,以更简洁的方式实现了惰性求值和状态管理。两者结合,既能优化内存使用,又能提升代码的可维护性。对于开发者而言,掌握这两个工具,不仅能解决日常开发中的常见问题(如大文件处理、无限序列生成),更能为理解高级编程范式(如协程、异步IO)打下坚实基础。
在实际应用中,建议优先使用生成器表达式或生成器函数来替代手动实现的迭代器,以降低代码复杂度。同时,结合 send()
和异常处理,可以进一步扩展生成器的功能边界,应对更复杂的场景需求。