← 返回题目列表

Python 生成器函数和普通函数有什么区别?

高频 中等 第 9 / 21 题 更新于 2026/07/27
生成器yield惰性计算迭代器

简化版

普通函数调用后会执行到结束并返回结果;生成器函数包含 yield,调用后不会立刻执行函数体,而是返回一个生成器对象,迭代时才逐步执行。生成器适合处理大数据流、惰性计算和需要保存执行状态的场景。

详细版

普通函数:

def get_numbers():
    return [1, 2, 3]

nums = get_numbers()

生成器函数:

def gen_numbers():
    yield 1
    yield 2
    yield 3

nums = gen_numbers()

gen_numbers() 返回的是生成器对象,不是列表。只有迭代它时,函数体才会执行:

for n in gen_numbers():
    print(n)

区别:

  • 普通函数用 return 返回最终结果。
  • 生成器函数用 yield 产出一个值,并暂停执行。
  • 下一次迭代时,从上次暂停的位置继续执行。
  • 生成器一次只产出一个值,内存占用更低。

生成器常用于读取大文件:

def read_lines(path):
    with open(path, encoding="utf-8") as f:
        for line in f:
            yield line.strip()

完整版教学

一、生成器函数的识别方式

只要函数体里出现 yield,它就是生成器函数:

def f():
    yield 1

调用它:

g = f()

此时函数体并没有执行,g 是一个生成器对象。

真正执行发生在:

next(g)

或者:

for item in g:
    ...

二、yield 做了两件事

yield 有两个作用:

  1. 产出一个值给调用方;
  2. 暂停当前函数执行状态。

例如:

def demo():
    print("start")
    yield 1
    print("middle")
    yield 2
    print("end")

执行:

g = demo()
print(next(g))
print(next(g))

第一次 next(g)

  • 打印 start
  • 遇到 yield 1
  • 返回 1
  • 函数暂停在这里。

第二次 next(g)

  • yield 1 后面继续;
  • 打印 middle
  • 遇到 yield 2
  • 返回 2

当函数执行结束后,再调用 next(g) 会抛出 StopIteration

三、生成器为什么省内存

普通函数如果返回列表:

def numbers(n):
    result = []
    for i in range(n):
        result.append(i)
    return result

n 很大时,列表会一次性占用大量内存。

生成器写法:

def numbers(n):
    for i in range(n):
        yield i

它不会一次性生成所有数据,而是用到一个产出一个。处理日志文件、数据库游标、网络流时,这种方式非常重要。

四、return 在生成器里的含义

生成器里也可以写 return

def gen():
    yield 1
    return

这里的 return 表示生成器结束。

如果写:

def gen():
    yield 1
    return "done"

"done" 会放到 StopIteration 的 value 里,但普通 for 循环不会把它当作一个迭代值输出。

面试里可以简单说:生成器主要靠 yield 产出值,return 用来结束生成器。

五、生成器表达式和生成器函数

生成器表达式:

squares = (x * x for x in range(10))

生成器函数:

def squares():
    for x in range(10):
        yield x * x

如果逻辑很简单,用生成器表达式即可;如果有复杂逻辑、异常处理、多个步骤,就用生成器函数。

六、生成器的常见坑

生成器是一次性迭代对象:

g = (x for x in range(3))
print(list(g))  # [0, 1, 2]
print(list(g))  # []

第一次已经把它消耗完了,第二次就没有内容。

如果需要重复遍历,应该重新创建生成器,或者把结果转成列表:

data = list(g)

但转列表会失去惰性计算的内存优势。

七、常见误区与追问

记忆钩子:普通函数像一次性把饭做好端上来,生成器像边吃边做;省内存来自“按需产出”,代价是执行状态和迭代生命周期要管理清楚。

  • 误区:调用生成器函数会立刻执行函数体。 只要函数体包含 yield,调用它得到的是生成器对象;真正执行发生在 next()for 循环或其他迭代消费动作中。
  • 追问:生成器为什么能保存执行状态? 每次遇到 yield,当前指令位置、局部变量和执行帧状态会暂停保留;下一次迭代从暂停点继续,而不是从函数开头重新执行。
  • 误区:生成器一定比列表快。 生成器通常更省内存,但每次迭代有状态恢复开销;数据量很小且需要多次随机访问时,列表可能更简单也更合适。
  • 追问:return value 在生成器里怎么取到? 它不会作为普通迭代值产出,而是放在 StopIteration.value 中;for 循环会吞掉结束异常,所以日常业务一般不依赖这个返回值。
  • 误区:生成器可以像列表一样反复遍历。 多数生成器是一次性迭代对象,消费完后再次 list(g) 会得到空列表;需要重复遍历就重新创建生成器或显式缓存结果。
  • 追问:生成器和迭代器是什么关系? 生成器对象本身就是迭代器,支持 __iter____next__;生成器函数是一种创建迭代器的便捷语法。

八、加强记忆

普通函数是“调用就跑完,返回一个结果”;生成器函数是“调用先得到生成器,迭代时边跑边 yield”。yield 既产出值,又保存暂停点。适合大文件、大数据流和流水线处理,但要记住生成器通常只能被消耗一次。