Python 中的迭代器(Iterator)和生成器(Generator)是 Python 中处理序列数据的核心概念,对于编写高效、优雅的代码至关重要。
1. 核心概念:可迭代对象、迭代器与生成器
要理解生成器,首先要理解迭代器;而要理解迭代器,首先要理解可迭代对象。这三者的关系是层层递进的。
| 概念 | 定义 | 关键方法或特征 |
| :--- | :--- | :--- |
| 可迭代对象 (Iterable) | 一个可以返回迭代器的对象。 它是数据的源头。 | 实现了 __iter__()
方法。 |
| 迭代器 (Iterator) | 一个同时实现了 __iter__()
和 __next__()
方法的对象。 它是数据的生产者,负责产生值并记录当前状态。 | 实现了 __iter__()
(返回自身) 和 __next__()
(返回下一个值) 方法。 |
| 生成器 (Generator) | 一种特殊的迭代器,由生成器函数或生成器表达式创建。 它提供了一种更简洁、优雅的构建迭代器的方式。 | 使用 yield
关键字或生成器表达式 (x for x in ...)
。 |
简单比喻:
- 可迭代对象就像一本书(数据源)。
- 迭代器就像一个书签(记录你读到哪里,并知道如何获取下一页)。
- 生成器是一个“智能书签”,它不仅能记录位置,还能按需“打印”出下一页的内容(动态生成值)。
2. 可迭代对象 (Iterable)
我们日常接触的绝大多数序列都是可迭代对象。
常见例子: list
, tuple
, str
, dict
, set
, file
等。
工作原理:
当你使用 for item in my_list:
循环时,Python 内部会:
- 调用
my_list.__iter__()
方法,获取一个迭代器对象。 - 重复调用这个迭代器对象的
__next__()
方法,将返回值赋给item
。 - 直到
__next__()
抛出StopIteration
异常,循环结束。
你可以用 iter()
函数手动获取一个可迭代对象的迭代器:
my_list = [1, 2, 3]
iterator = iter(my_list) # 等同于 iterator = my_list.__iter__()
print(next(iterator)) # 输出 1 (等同于 iterator.__next__())
print(next(iterator)) # 输出 2
print(next(iterator)) # 输出 3
print(next(iterator)) # 抛出 StopIteration 异常
3. 迭代器 (Iterator)
迭代器是“值流”的抽象。它一定是可迭代的(因为它有 __iter__
),但可迭代对象不一定是迭代器(例如列表不是迭代器,但它的 __iter__()
返回的才是)。
如何创建自定义迭代器?
创建一个类,实现 __iter__()
和 __next__()
方法。
示例:创建一个返回数字的迭代器,直到达到最大值
class MyCounter:
def __init__(self, max_value):
self.max = max_value
self.current = 0
def __iter__(self):
# __iter__ 必须返回一个迭代器对象,因为它自己就是迭代器,所以返回 self
return self
def __next__(self):
if self.current < self.max:
self.current += 1
return self.current
else:
# 必须抛出 StopIteration 来终止迭代
raise StopIteration
# 使用
counter = MyCounter(3)
for num in counter:
print(num)
# 输出:
# 1
# 2
# 3
缺点: 用类来实现迭代器比较繁琐,需要管理状态(如 self.current
)和异常。这就是生成器出现的原因。
4. 生成器 (Generator)
生成器是创建迭代器的“语法糖”,它让代码变得异常简洁。
方式一:生成器函数 (Generator Function)
使用 def
定义函数,但在函数体中使用 yield
关键字而不是 return
。
yield
的关键行为:
- 当函数被调用时,它不会立即执行,而是返回一个生成器对象(一种迭代器)。
- 当第一次调用
next()
时,函数从开头开始执行,直到遇到yield
语句。yield
会暂停函数的执行,并将后面的值返回给调用者。 - 下次再调用
next()
,函数会从上次yield
语句之后的位置恢复执行,直到再次遇到yield
。 - 如果函数执行到
return
语句或函数结尾,会抛出StopIteration
异常。
示例:用生成器函数实现上面的 MyCounter
def my_counter_generator(max_value):
current = 0
while current < max_value:
current += 1
yield current # 在此处暂停,返回 current 的值
# 使用
counter_gen = my_counter_generator(3) # 此时函数并未执行,只是创建了生成器对象
print(next(counter_gen)) # 输出 1 (函数执行到 yield current 处暂停)
print(next(counter_gen)) # 输出 2 (从 yield 之后恢复,继续 while 循环)
print(next(counter_gen)) # 输出 3
print(next(counter_gen)) # 抛出 StopIteration
# 更常见的用法是用于 for 循环
for num in my_counter_generator(3):
print(num)
方式二:生成器表达式 (Generator Expression)
语法与列表推导式类似,但使用圆括号 ()
而不是方括号 []
。
- 列表推导式:
[x*x for x in range(5)]
→[0, 1, 4, 9, 16]
(立即计算所有结果,占用内存) - 生成器表达式:
(x*x for x in range(5))
→ 生成器对象 (惰性计算,节省内存)
# 生成器表达式
gen_exp = (x * x for x in range(5))
print(gen_exp) # <generator object <genexpr> at 0x...>
for i in gen_exp:
print(i) # 输出 0, 1, 4, 9, 16
# 可以直接作为函数参数
sum_of_squares = sum(x*x for x in range(5))
print(sum_of_squares) # 输出 30
5. 核心优势与使用场景
- 惰性计算 (Lazy Evaluation):这是最重要的优势。生成器不会一次性生成所有数据并存入内存,而是按需生成一个值、处理一个值。这对于处理大规模甚至无限的数据流至关重要。
- 场景: 读取几个 GB 的日志文件,使用生成器可以逐行读取处理,而不是一次性加载到内存导致崩溃。
- 代码更简洁:用几行生成器代码就能替代一个复杂的迭代器类。
- 表示无限序列:由于是惰性的,可以轻松表示无限序列。
def infinite_sequence():
num = 0
while True:
yield num
num += 1
# 使用
for i in infinite_sequence():
if i > 100:
break
print(i)
- 协同工作与管道 (Pipeline):多个生成器可以连接起来,形成高效的数据处理管道。
# 一个简单的管道示例:求0-99中所有偶数的平方和
numbers = (x for x in range(100)) # 生成器1:产生数字
even_numbers = (x for x in numbers if x % 2 == 0) # 生成器2:过滤偶数
squared = (x*x for x in even_numbers) # 生成器3:计算平方
result = sum(squared) # 求和
print(result)
数据像水流一样逐个通过每个生成器,内存中始终只有当前处理的数据。
总结与对比
| 特性 | 迭代器 (Iterator) | 生成器 (Generator) |
| :--- | :--- | :--- |
| 实现方式 | 通过类实现 __iter__
和 __next__
| 通过函数 + yield
或生成器表达式 |
| 代码简洁性 | 相对繁琐,需手动管理状态 | 极其简洁,状态由函数暂停/恢复自动管理 |
| 内存效率 | 高(惰性计算) | 高(惰性计算) |
| 功能强度 | 基础,可定义复杂逻辑 | 更专注于“按需生成值”这一场景 |
| 关系 | 生成器是一种特殊的迭代器 | 所有生成器都是迭代器 |
简单决策:当你需要创建一个自定义的迭代器时,99% 的情况都应该首选生成器。 它用更少的代码实现了同样的功能,并且更加 Pythonic。