在处理大量数据或需要按需生成序列时,迭代器和生成器是 Python 中最常用、最优雅的工具。它们让你能用统一的方式遍历任意对象,同时大幅节省内存。
迭代器协议与可迭代对象
Python 中的迭代基于一个简单的协议:
- 可迭代对象:实现了
iter方法,该方法返回一个迭代器对象。常见的可迭代对象包括list、tuple、dict、set、str、文件对象等。 - 迭代器对象:实现了
iter方法(通常返回自身)和next方法。next方法在每次调用时返回下一个元素,当没有元素时抛出StopIteration异常。
可以用 iter() 函数获取迭代器,用 next() 函数获取下一个元素:
nums = [1, 2, 3]
it = iter(nums)
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
# next(it) 将引发 StopIteration
for 循环就是基于这个协议的语法糖:
for item in nums:
print(item)
等价于:
it = iter(nums)
while True:
try:
item = next(it)
except StopIteration:
break
print(item)
迭代器的特点
- 惰性计算:只在需要时才生成下一个值,不像列表那样一次性把所有数据加载到内存。
- 一次性使用:迭代器只能向前遍历,走完一次后便耗尽,再次使用需重新创建。
- 节省内存:对于海量数据或无限序列,迭代器不会导致内存爆炸。
你可以为自定义类实现迭代器协议,使其支持 for 循环:
class CountDown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
num = self.current
self.current -= 1
return num
for n in CountDown(3):
print(n) # 3, 2, 1
生成器:最简单的迭代器
手动编写 iter 和 next 方法还是略显繁琐。生成器提供了一种更简洁的创建迭代器的方式:直接编写一个包含 yield 关键字的函数。
- 生成器函数:调用时会返回一个生成器对象(一个特殊的迭代器),并不立即执行函数体。每次调用
next()或迭代时,函数会执行到yield语句,返回一个值并暂定执行,下一次调用再从暂停处继续。 - 使用
yield取代return,且可以出现多次。
def count_down(n):
while n > 0:
yield n
n -= 1
for num in count_down(3):
print(num) # 3, 2, 1
生成器函数的核心优势:
- 自动保存状态:函数局部变量、执行位置在 yield 时自动保存,恢复时完全还原,无需手动维护状态。
- 惰性求值:按需生成值,示例中大范围循环
while n > 0并不会一次性计算出所有值并存入内存。 - 代码简洁:相比手动实现迭代器类,生成器函数通常只要几行。
生成器表达式
生成器表达式可看作列表推导式的惰性版本,语法是将方括号改为圆括号:
squares = (x*x for x in range(1000000))
# squares 是一个生成器对象,不会立即创建 100 万个平方值
print(next(squares)) # 0
print(next(squares)) # 1
# 常直接用在迭代或汇总函数中
total = sum(x*x for x in range(1000000))
生成器表达式适用于需要临时产生序列,又不想浪费内存的场合,尤其在数据流水线中与 sum(), min(), max(), any(), all() 等内建函数配合极为方便。
实用场景:处理大文件
逐行读取大文件正是迭代器的典型应用。文件对象本身是可迭代的,它一行一行地产生数据,不会一次性将整个文件读入内存:
with open('large_file.txt') as f:
for line in f: # 每次只读取一行
process(line)
若你需要对每一行进行过滤或转换,可以在中间加入生成器函数或生成器表达式,构建出一条数据处理流水线:
def non_empty_lines(lines):
for line in lines:
if line.strip():
yield line.strip()
with open('data.txt') as f:
clean_lines = non_empty_lines(f)
for line in clean_lines:
print(line)
进阶:生成器与协程
生成器不仅可以产出值(yield),还能接收值,甚至同时收发,这使其可以演化成协程。在 Python 3.5 引入 async/await 之前,协程就是基于增强的生成器实现的。例如使用 yield 表达式接收来自外部的值:
def echo():
while True:
received = yield
print(f"收到: {received}")
e = echo()
next(e) # 启动生成器,执行到 yield 暂停
e.send("Hello") # 发送值,输出“收到: Hello”
e.send("World") # 输出“收到: World”
不过,在现代 Python 中,真正的异步编程已主要使用 asyncio 和 async/await,生成器协程模式主要用于底层框架或兼容老代码。生成器最常用的身份仍然是最简便、最高效的内存友好型迭代器。
小结
- 任何支持
for循环的对象都是可迭代对象,背后是迭代器协议。 - 生成器是创建迭代器的快捷方式,用
yield可轻松实现惰性求值。 - 处理大数据流或不适合一次性加载的数据时,优先考虑生成器,既能节省内存,又能保持代码清晰。