Python 生成器函数和普通函数有什么区别?
简化版
普通函数调用后会执行到结束并返回结果;生成器函数包含 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 有两个作用:
- 产出一个值给调用方;
- 暂停当前函数执行状态。
例如:
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 既产出值,又保存暂停点。适合大文件、大数据流和流水线处理,但要记住生成器通常只能被消耗一次。