生成器

ZaynPei Lv6

生成器是一种特殊的迭代器,它不需要你手动去实现迭代器协议(即 iter() 和 next() 方法)。它允许你用一种更简单、更像普通函数的方式来生成一个值的序列。

生成器的核心思想是 “懒加载” (Lazy Evaluation) 或 “延迟计算”。它不会一次性在内存中创建并存储所有元素,而是在你请求下一个元素时才实时生成它。

为什么要使用生成器?

生成器最主要的优点是内存效率极高。想象一下,你需要处理一个包含一百万个元素的序列。

  • 使用列表:会立即在内存中创建并存储这一百万个元素,占用大量内存。
  • 使用生成器:它只在你需要时才逐一生成元素。在任何时刻,内存中只有一个元素存在,极大地节约了内存资源。

这使得生成器非常适合处理:

  • 大规模数据集:如大型日志文件、数据库查询结果等。
  • 无限序列:例如,生成一个永不停止的斐波那契数列。
  • 数据流处理:在数据处理管道中,数据可以像水流一样通过各个生成器节点,而无需将整个数据集加载到内存中。

创建生成器

主要有两种方式可以创建生成器:

  1. 生成器函数 (Generator Functions) 这是最常见的方式。一个普通的函数,只要包含了 yield 关键字,它就变成了一个生成器函数。

yield 关键字:这是生成器的魔法所在。yield 的作用类似于 return,但它并不会终止函数。相反,它会“暂停”函数的执行,并将其当前状态(包括局部变量)保存下来,然后将 yield 后面的值返回给调用者。当下次请求值时(例如通过 next()for 循环),函数会从上次暂停的地方继续执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def countdown(n):
print("开始倒计时!")
while n > 0:
yield n # 暂停并返回 n 的值
n -= 1
print("倒计时结束!")

# 1. 调用生成器函数,返回一个生成器对象,此时函数内的代码还未执行
c = countdown(3)
print(type(c)) # <class 'generator'>

# 2. 第一次调用 next(),函数开始执行,直到遇到第一个 yield
print(next(c)) # 输出 "开始倒计时!" 和 3

# 3. 第二次调用 next(),函数从上次暂停处继续执行
print(next(c)) # 输出 2

# 4. 第三次调用 next()
print(next(c)) # 输出 1

# 5. 第四次调用 next(),循环结束,函数执行完毕
# 由于没有更多的 yield,函数会隐式地抛出 StopIteration 异常
try:
next(c) # 输出 "倒计时结束!" 然后抛出异常
except StopIteration:
print("生成器已耗尽。")

注意, 调用generator函数会创建一个generator对象,多次调用generator函数会创建多个相互独立的generator。假如在上面的代码中这样做:

1
2
print(next(countdown(3))) # 每次调用都会创建一个新的生成器对象, 所以每次都是输出 3
print(next(countdown(3))) # 每次调用都会创建一个新的生成器对象, 所以每次都是输出 3
正确的写法是创建一个generator对象,然后不断对这一个generator对象调用next()或者使用for循环.

  1. 生成器表达式 (Generator Expressions) 生成器表达式是列表生成式的近亲,语法上非常相似,但它返回的是一个生成器对象,而不是一个列表。

语法:将列表生成式的 [] 替换为 () 即可

1
2
3
4
5
6
7
8
9
10
11
# 列表生成式
my_list = [x * x for x in range(5)]
print(my_list) # 输出: [0, 1, 4, 9, 16]

# 生成器表达式
my_generator = (x * x for x in range(5))
print(my_generator) # 输出: <generator object <genexpr> at 0x...>

# 像使用其他迭代器一样使用它
for num in my_generator:
print(num, end=' ') # 输出: 0 1 4 9 16