Python 中的迭代器和生成器都是用于遍历数据集合的重要概念,但它们之间有一些关键的区别:
迭代器 (Iterator)
- 实现了
__iter__()和__next__()协议的对象 ⇒ 迭代器。 - 当没有更多的元素时,它会抛出
StopIteration异常。 - 重要特性 —— 惰性求值,只在需要下一个数据时才计算,省内存。
生成器 (Generator)
- 含有
yield函数 ⇒ 生成器函数。使用时,先正常函数调用,使生成器函数在yield处停下。后续通过next函数来从 yield 处继续运行。 - 支持惰性求值,可以保存状态。生成下一个值时会保留之前的局部变量状态和执行位置。
- 生成器还可以通过
send()方法接收外部值,这允许生成器在生成过程中接收输入。 - 生成器在 Python 的写法是用小括号括起来,(i for i in range(100000000)),即初始化了一个生成器。
- 生成器函数还可以与 @contextmanager 结合,作为上下文管理器。具体参考 Python 上下文管理器 & with 块。
def generator(k):
i = 1
while True:
yield i ** k
i += 1
gen_1 = generator(1)
gen_3 = generator(3)
print(gen_1)
print(gen_3)
def get_sum(n):
sum_1, sum_3 = 0, 0
for i in range(n):
next_1 = next(gen_1)
next_3 = next(gen_3)
print('next_1 = {}, next_3 = {}'.format(next_1, next_3))
sum_1 += next_1
sum_3 += next_3
print(sum_1 * sum_1, sum_3)
get_sum(8)def index_generator(L, target):
for i, num in enumerate(L):
if num == target:
yield i
print(list(index_generator([1, 6, 2, 4, 5, 2, 8, 6, 3, 2], 2)))
########## 输出 ##########
[2, 5, 9]唯一需要强调的是, index_generator 会返回一个 Generator 对象,需要使用 list 转换为列表后,才能用 print 输出。
内存占用
def test_iterator():
show_memory_info('initing iterator')
list_1 = [i for i in range(100000000)]
show_memory_info('after iterator initiated')
print(sum(list_1))
show_memory_info('after sum called')
def test_generator():
show_memory_info('initing generator')
list_2 = (i for i in range(100000000))
show_memory_info('after generator initiated')
print(sum(list_2))
show_memory_info('after sum called')
%time test_iterator()
%time test_generator()
########## 输出 ##########
initing iterator memory used: 48.9765625 MB
after iterator initiated memory used: 3920.30078125 MB
4999999950000000
after sum called memory used: 3920.3046875 MB
Wall time: 17 s
initing generator memory used: 50.359375 MB
after generator initiated memory used: 50.359375 MB
4999999950000000
after sum called memory used: 50.109375 MB
Wall time: 12.5 s区别总结
-
定义方式:
- 迭代器通常由类实现,需要定义
__iter__()和__next__()方法。 - 生成器由包含
yield关键字的函数定义。
- 迭代器通常由类实现,需要定义
-
状态保存:
- 迭代器通常不保存状态,每次调用
__next__()都会从上次停止的地方继续。 - 生成器会记住函数的执行状态,并且可以在多次调用之间保持局部变量的状态。
- 迭代器通常不保存状态,每次调用
-
使用场景:
- 迭代器适用于需要手动控制何时获取下一个元素的情况。
- 生成器更适合于创建复杂的序列,特别是当这些序列很大或者无穷无尽时。
-
创建实例:
- 创建迭代器通常需要显式实例化一个类。
- 创建生成器只需调用生成器函数即可得到生成器对象。
-
发送值:
- 迭代器本身不支持向迭代过程中发送值。
- 生成器可以通过
send()方法发送值,从而改变生成器内部的状态。
总的来说,生成器是一种更高级的抽象,它简化了编写惰性求值和状态保存的代码的过程。迭代器则是一个更基础的概念,它定义了如何遍历集合的基本规则。在实际编程中,生成器由于其简单易用以及强大的功能,经常被用来替代传统的迭代器实现。