基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

什么是Python中的生成器?

知识点图片

在 Python 中,生成器(Generator) 是一种特殊的迭代器(Iterator)

简单来说,生成器允许你定义一个可以像列表一样进行遍历(迭代)的函数,但它不会一次性把所有数据都加载到内存中,而是用一个生成一个(即“惰性求值”)。

以下是关于生成器的详细解释:


1. 核心概念:为什么要用生成器?

想象你需要处理 1 亿个数字:

  • 列表(List): Python 会立刻在内存中创建这 1 亿个数字。这会占用巨大的内存,甚至导致程序崩溃。
  • 生成器(Generator): Python 不会立刻创建数字。它只是记住了生成的规则。当你需要第 1 个数字时,它算出来给你;当你需要第 2 个时,它再算第 2 个。它几乎不占内存。

2. 如何创建生成器?

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

方法 A:生成器函数(使用 yield 关键字)

这是最常见的方法。如果一个函数中包含了 yield 关键字,那么这个函数就变成了一个生成器函数。

  • return:函数执行完毕,返回结果,销毁局部变量。
  • yield:函数暂停执行,返回一个值,但保留当前的局部变量和执行状态。下次调用时,从暂停的地方继续执行。
python
def simple_generator():
    print("开始执行...")
    yield 1          # 暂停,返回 1
    print("回来继续执行...")
    yield 2          # 暂停,返回 2
    print("最后一次执行...")
    yield 3          # 暂停,返回 3

# 调用函数不会立即执行代码,而是返回一个生成器对象
gen = simple_generator()

# 使用 next() 获取值
print(next(gen)) # 输出: 开始执行... \n 1
print(next(gen)) # 输出: 回来继续执行... \n 2
print(next(gen)) # 输出: 最后一次执行... \n 3
# print(next(gen)) # 再次调用会抛出 StopIteration 异常,表示没有数据了

方法 B:生成器表达式

类似于列表推导式(List Comprehension),但是用圆括号 () 代替方括号 []

python
# 列表推导式(立即生成所有数据,占内存)
my_list = [x * x for x in range(10)] 

# 生成器表达式(不立即生成,几乎不占内存)
my_gen = (x * x for x in range(10)) 

print(my_gen) # 输出: <generator object ...>
print(next(my_gen)) # 输出: 0
print(next(my_gen)) # 输出: 1

3. 如何使用生成器?

通常我们不会手动调用 next(),而是直接在 for 循环中使用它。for 循环会自动处理 StopIteration 异常。

python
def fibonacci(n):
    a, b = 0, 1
    for _ in range(n):
        yield a
        a, b = b, a + b

# 像使用列表一样使用生成器
for num in fibonacci(5):
    print(num)

4. 生成器的优缺点

优点:

  1. 节省内存(Memory Efficient): 这是最大的优势。处理大数据文件或无限数据流时非常有用。
  2. 惰性求值(Lazy Evaluation): 只有在需要值的时候才计算,避免了不必要的计算。
  3. 可以表示无限流: 因为不需要一次性存储,你可以写一个永远产生数据的生成器(例如无限斐波那契数列),只要你不停止请求,它就一直生成。

缺点:

  1. 只能遍历一次: 生成器就像水流,流过去就没了。如果你想再次遍历,必须重新创建一个新的生成器对象。
  2. 无法通过索引访问: 你不能写 gen[5],因为第 5 个元素还没算出来呢,你必须先算出前 4 个。
  3. 无法获取长度: 不能使用 len(gen)

5. 总结对比

特性 列表 (List) 生成器 (Generator)
创建方式 [x for x in range(10)] (x for x in range(10))
内存占用 高(存储所有数据) 极低(只存储状态)
计算方式 立即计算所有结果 需要时才计算 (Lazy)
访问方式 索引访问 list[0],可多次遍历 只能 next() 或迭代,只能遍历一次
适用场景 数据量小,需要频繁随机访问 数据量大,只需要顺序读取

举个通俗的例子

  • 列表就像是买了一整箱做好的饼干。你需要找地方放这箱饼干(占内存),不管你吃不吃,钱都花了(已计算)。
  • 生成器就像是请了一位厨师。你饿了说“给我一块饼干”,厨师现场做一块给你。你不吃,厨师就站着不动。不需要仓库放饼干,但每次吃都需要一点时间现做。
00:00
00:00