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

区别总结

  1. 定义方式

    • 迭代器通常由类实现,需要定义 __iter__()__next__() 方法。
    • 生成器由包含 yield 关键字的函数定义。
  2. 状态保存

    • 迭代器通常不保存状态,每次调用 __next__() 都会从上次停止的地方继续。
    • 生成器会记住函数的执行状态,并且可以在多次调用之间保持局部变量的状态。
  3. 使用场景

    • 迭代器适用于需要手动控制何时获取下一个元素的情况。
    • 生成器更适合于创建复杂的序列,特别是当这些序列很大或者无穷无尽时。
  4. 创建实例

    • 创建迭代器通常需要显式实例化一个类。
    • 创建生成器只需调用生成器函数即可得到生成器对象。
  5. 发送值

    • 迭代器本身不支持向迭代过程中发送值。
    • 生成器可以通过 send() 方法发送值,从而改变生成器内部的状态。

总的来说,生成器是一种更高级的抽象,它简化了编写惰性求值和状态保存的代码的过程。迭代器则是一个更基础的概念,它定义了如何遍历集合的基本规则。在实际编程中,生成器由于其简单易用以及强大的功能,经常被用来替代传统的迭代器实现。

参考链接

19 | 深入理解迭代器和生成器-Python核心技术与实战-极客时间