生成器和普通函数有什么区别?
1️⃣ 执行方式:一次性 vs 可暂停的流¶
普通函数一调到底,return 之后栈帧销毁,啥也不剩。
生成器函数碰到 yield 会暂停,把值“吐”出去,下次 next() 或迭代再从暂停处继续。它返回的是一个生成器对象,而不是计算结果。
def gen():
yield 1
yield 2
yield 3
g = gen() # 这里还没执行函数体!
print(next(g)) # 执行到第一个 yield,输出 1,暂停
print(next(g)) # 继续执行到第二个 yield,输出 2
▶️ 普通函数是“一次性交付”,生成器是“随用随取”。
2️⃣ 内存占用:全量装载 vs 惰性产出¶
这一点在处理大文件或流数据时是碾压级的。
-
普通函数:
return一个列表,所有数据同时在内存里。读 10GB 日志,内存直接爆。 -
生成器:每次只 yield 一行,内存里永远只保留当前行,可以处理无限流。
# 普通函数,读完整个文件才返回
def read_file(path):
lines = []
with open(path) as f:
for line in f:
lines.append(line)
return lines
# 生成器,一行一行产出
def read_file_gen(path):
with open(path) as f:
for line in f:
yield line
💡 这也是为什么很多 Python 标准库把原来返回列表的接口改成了返回生成器(如 range vs xrange,map/filter 的返回类型)。
3️⃣ 状态保存:局部变量的生命周期¶
普通函数每次调用,局部变量重生,彼此独立。
生成器函数的局部变量在 yield 暂停后仍然存活,下次 next() 可以接着用。
def counter():
n = 0
while True:
n += 1
yield n
c = counter()
print(next(c)) # 1
print(next(c)) # 2 ← n 的值被记住了
这个状态保持的能力,让生成器可以天然地描述“有状态的流处理管道”,而不需要你去写一个实现了 iter 和 next 的类。
4️⃣ 双向通信:不只是产出,还能接收¶
生成器还有个普通函数做不到的能力:通过 send() 向暂停处传入值,这让它可以扮演轻量级协程。
def echo():
while True:
received = yield
print(f"收到: {received}")
g = echo()
next(g) # 先推到第一个 yield
g.send("hello") # 传入值,并继续执行到下一个 yield
另外 throw() 和 close() 可以在外部控制生成器的异常和终止,普通函数完全没有这种“外部干预执行”的能力。