Python 生成器(Generators)详解

在Python中,生成器是一个非常强大且高效的工具,能够让你以惰性(lazy)方式迭代处理数据。生成器可以节省内存,因为它们不会一次性生成所有的数据,而是按需生成。这使得生成器在处理大量数据时非常有用,尤其是当数据集庞大到不能一次性加载到内存时。


1. 什么是生成器?

生成器是一个返回迭代器的函数,它使用了 yield 关键字来返回数据。与普通函数不同的是,当生成器函数调用时,它并不会立刻执行所有的代码并返回结果,而是返回一个生成器对象。每次调用生成器的 __next__() 方法时,生成器才会继续执行代码,直到遇到 yield 语句或者结束。

通过 yield 返回的每个值都被保存在生成器的状态中,当 next() 被调用时,执行会从上次返回 yield 语句的地方继续。


2. 创建生成器

2.1 使用 yield 关键字

最简单的生成器通过 yield 关键字创建:

def count_up_to(max):

    count = 1

    while count <= max:

        yield count # 每次调用时返回一个新的值 count += 1

调用这个生成器函数不会直接返回结果,而是返回一个生成器对象:

counter = count_up_to(5)

要从生成器中获取值,可以使用 next() 或者在循环中迭代它:

print(next(counter)) # 输出: 1

print(next(counter)) # 输出: 2

或者使用 for 循环自动迭代:

for num in count_up_to(5):

    print(num)

输出:

1 2 3 4 5


2.2 生成器表达式

除了使用 yield 关键字创建生成器,我们还可以使用生成器表达式(generator expressions),它类似于列表推导式,但使用圆括号而不是方括号:

gen = (x * x for x in range(5))

使用 next()for 循环来获取生成器表达式的值:

print(next(gen)) # 输出: 0

print(next(gen)) # 输出: 1

for value in gen:

    print(value)

输出:

4 9 16


3. 生成器的工作原理

生成器函数在调用时返回一个生成器对象,并且函数的执行是惰性执行的。每次调用 next() 时,生成器从上次 yield 返回的位置继续执行,直到遇到下一个 yield 或结束。

3.1 状态保存

生成器函数的一个重要特性是它会记住每次执行的状态,这意味着生成器在执行时会保存局部变量、指令指针和栈帧。

def count_up_to(max):

    count = 1

    while count <= max:

        print(f"Yielding {count}")

        yield count

        count += 1

执行以下代码:

counter = count_up_to(3)

print(next(counter)) # 输出:Yielding 1, 1

print(next(counter)) # 输出:Yielding 2, 2

在第一次调用 next(counter) 时,count 会从 1 开始,执行到 yield 1 后,返回 1 并暂停执行。第二次调用时,生成器从上次暂停的地方继续,count 变为 2,直到遇到下一个 yield


4. 生成器的优点

4.1 内存效率

生成器的一个主要优点是它们比列表、元组等数据结构更加节省内存,因为它们不会一次性生成所有数据。数据是惰性地生成的,只有在需要时才会计算。

假设我们需要生成一个非常大的序列,普通的列表可能会占用大量内存,而使用生成器就能避免这一问题。

# 使用列表

numbers = [x * x for x in range(1000000)] # 会占用大量内存

# 使用生成器

numbers = (x * x for x in range(1000000)) # 不会占用大量内存

4.2 延迟计算

生成器仅在迭代时计算下一个值,避免了计算不必要的元素。这对于那些需要根据实际需求生成值的情况非常有用。

例如,处理一个非常大的数据流,生成器可以动态生成每一项,而不会一次性读取整个数据流,从而减少处理时间和内存消耗。

4.3 支持无限序列

生成器非常适合表示和处理无限序列,因为它们不会将所有值都存储在内存中。你可以创建一个无限的序列,而生成器会按需生成每个值。

例如,我们可以创建一个生成斐波那契数列的生成器:

def fibonacci():

    a, b = 0, 1

    while True:

        yield a

        a, b = b, a + b

每次调用 next() 会返回下一个斐波那契数:

fib = fibonacci()

print(next(fib)) # 输出: 0

print(next(fib)) # 输出: 1

print(next(fib)) # 输出: 1

print(next(fib)) # 输出: 2


5. 生成器的应用场景

生成器非常适合以下几种情况:

  1. 大数据处理:当数据集非常大,无法一次性加载到内存时,生成器能够按需生成数据,避免内存消耗。

  2. 流式处理:生成器在处理流式数据时非常有用,比如处理日志文件、网络数据、文件读取等。

  3. 无限序列:生成器能够表示无限序列,这在生成需要持续计算的序列时非常有用。

  4. 并发编程:生成器可以在协程或异步编程中用于暂停和恢复执行,配合 async/await 语法,可以实现高效的并发任务调度。


6. 生成器与迭代器的关系

生成器是迭代器的一个特殊实现。一个迭代器必须实现 __iter__()__next__() 方法,而生成器函数自动实现了这两个方法。

# 普通迭代器
class MyIterator:
    def __init__(self, start, end):
        self.current = start
        self.end = end
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.current > self.end:
            raise StopIteration
        self.current += 1
        return self.current - 1

# 生成器
def my_generator(start, end):
    current = start
    while current <= end:
        yield current
        current += 1

虽然二者的表现相似,但生成器更简洁、易读。


7. 总结

生成器是Python中非常强大的工具,它们通过 yield 按需生成数据,节省内存并提高效率。通过生成器,你可以处理大量数据、无限序列以及流式数据,且支持惰性计算,避免不必要的计算和内存占用。

无论是在处理大数据、流式数据,还是在编写高效的算法时,生成器都能发挥巨大的作用。

作者:威桑

物联沃分享整理
物联沃-IOTWORD物联网 » Python中的生成器

发表回复