Python 中的生成器与迭代器:原理剖析与应用场景

less than 1 minute read

Published:

在 Python 相关的面试题里,生成器(Generator)和迭代器(Iterator)是两个经常考察的语法。

实际工作里,这两个功能广泛用于处理大规模数据、流式数据以及在内存受限的环境下进行高效计算。虽然这两个概念关系密切,但它们的使用场景和实现方式有所不同。

这里深入探讨下它们的区别、特点以及各自的应用场景。

0x01 迭代器 Iterator

迭代器是一种按需生成数据的对象。简单来说,迭代器是一个可以通过反复调用 next() 方法逐个获取数据元素的对象

当所有数据都被遍历完后,迭代器会抛出 StopIteration 异常。

核心特性

  • 惰性计算:迭代器不会一次性生成所有数据,而是按需计算每个元素,直到请求下一个数据时才会计算出下一个结果。
  • 一次性使用:一旦迭代器的数据被迭代完,它就不能被重置或重新迭代。如果需要重新开始迭代,必须创建新的迭代器。
  • 实现方式:迭代器实现了 __iter__()__next__() 方法。

使用场景

  • 大数据流:例如读取大文件的内容,或者处理一个巨大的数据库查询结果。使用迭代器可以避免一次性加载全部数据到内存。
  • 无限数据流:例如生成一个无限大的序列(如自然数),由于数据是惰性计算的,因此没有内存消耗问题。

创建一个简单的数字倒数迭代器:

class Countdown:
    def __init__(self, start):
        self.start = start

    def __iter__(self):
        self.current = self.start
        return self

    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        self.current -= 1
        return self.current

counter=Countdown(5)
print(next(counter))  # 输出: 4
print(next(counter))  # 输出: 3

0x02 生成器 Generator

生成器是 Python 中用于创建迭代器的一个简便工具。

通过生成器函数和 yield 关键字,生成器可以动态生成数据,每次调用 next() 时返回一个数据值,而生成器函数会在每次 yield 时暂停执行,直到下一次调用 next()

核心特性

  • 惰性计算:生成器和迭代器一样,都是惰性计算的。它不会一次性生成所有数据,而是边迭代边生成。
  • 易于创建:相比于手动实现一个迭代器,生成器提供了一种简洁的方式来创建迭代器。只需使用 yield 关键字即可。
  • 一次性使用:生成器在生成完数据后,也不能被重置。如果需要重新开始迭代,必须重新创建一个生成器实例。

使用场景

  • 大规模数据:生成器非常适用于需要逐步计算数据的场景。例如从文件中读取数据或从数据库中查询数据时,生成器可以有效节省内存
  • 无限序列:生成器适用于表示无限的数据流,如自然数、斐波那契数列等,它能够动态生成数据而不会消耗过多内存。

使用方法

关于使用生成器,本质的方法就是:

使用yield可以将一个函数对象升级为一个生成器对象

在函数中使用 yield 关键字,可以将一个普通的函数变成一个 生成器函数,而该函数返回的是生成器对象

下面是使用生成器构造斐波那契数列的方法

def fibonacci(max):
    a, b = 0, 1
    while a < max:
        yield a
        a, b = b, a + b

# 创建生成器
fib_gen = fibonacci(10)

# 使用 next() 获取下一个值
print(next(fib_gen))  # 输出: 0
print(next(fib_gen))  # 输出: 1

此外,Python 中在[]

0x03 生成器与迭代器的区别

尽管生成器和迭代器有很多相似之处,尤其是在数据的惰性计算方面,但它们之间存在一些关键的差异。

特性迭代器(Iterator)生成器(Generator)
实现方式需要实现 __iter__()__next__() 方法使用 yield 关键字创建生成器函数
创建方式需要手动创建类并实现迭代器接口使用简单的 yield 来创建生成器
内存效率按需计算,不预先加载所有数据按需计算,且内存占用极低,尤其适合大数据
数据流类型可以是有限或无限的流通常用于生成有限或无限的数据流
使用简便性相对较复杂,必须手动实现迭代器接口使用 yield 更加简洁直观
适用场景适合复杂的迭代任务,如自定义逻辑的迭代适合简单的按需生成数据,特别是递归或迭代任务

迭代器使用场景

  • 复杂的数据处理:当你需要手动控制数据的生成过程或处理逻辑时,迭代器提供了更大的灵活性。
  • 不可变数据流:如果数据流是不可变的,并且你需要精细控制每次迭代的细节,那么手动创建迭代器是更合适的选择。

生成器使用场景

  • 简洁的数据生成:当你仅仅需要按需生成数据,尤其是递归或简单的数学计算时,生成器提供了简便且高效的解决方案。
  • 无限序列生成:生成器非常适合生成无限序列或大数据流的情况,例如生成无限的斐波那契数列、自然数等。

生成器与迭代器的选择

  • 如果只需要处理一个简单的数据流且不需要复杂的逻辑或状态维护,那么生成器是一个更好的选择。它不仅语法简单,而且内存效率高。
  • 如果数据流需要更复杂的控制,或者你想实现一个自定义的数据流生成过程(如需要更多的状态管理),那么你可能需要实现一个完整的迭代器。