在Python编程中,生成器(generator)是一种特殊的迭代器,它能够按需生成值而不是一次性构建完整的数据集合。普通函数通过return语句将结果整体返回,执行完毕后内部状态全部释放;而生成器函数使用yield语句,每次被请求下一个值时才运行到下一个yield点,并暂停保留当前的局部变量与指令位置。这种机制让生成器非常适合处理大规模或潜在无限的数据流。

一、生成器的定义与基本形态
生成器本质上是一个实现了迭代器协议(具备__iter__与__next__方法)的对象。在Python里,写起来最直观的生成器就是生成器函数:只要在函数体中出现yield关键字,该函数被调用时就不会立即执行函数体,而是返回一个生成器对象。之后每对该对象调用next(),函数便从暂停处继续,直到遇到下一个yield将值传出,或函数正常结束抛出StopIteration。
除了生成器函数,还有一种生成器表达式,语法类似列表推导但用圆括号包裹。例如(x*x for x in range(10))就是一个生成器对象,它同样惰性计算。两种方式底层都依赖Python的帧对象挂起能力,区别只在于书写形式。对于简单映射过滤,生成器表达式更简洁;对于带复杂控制流的逻辑,生成器函数更清晰。
def count_up_to(n):
i = 0
while i <= n:
yield i
i += 1
gen = count_up_to(3)
print(next(gen)) # 输出 0
print(next(gen)) # 输出 1
print(list(gen)) # 输出 [2, 3]
二、普通函数的执行模型
普通函数采用典型的调用栈模型。当解释器遇到函数调用,会创建新的栈帧,从头执行函数体,直到碰见return或函数末尾,此时栈帧被销毁,局部变量不复存在,调用者拿到return指定的返回值。若没有显式return,则默认返回None。这种“运行到底”的方式在处理已知规模的小数据集合时直观高效。
但普通函数无法在中途“停住”等外部再来取下一个结果。假如要生成十亿个整数,用普通函数返回list,内存会瞬间被占满;而业务逻辑往往只需要逐个消费。此外,普通函数每次调用都是独立全新的执行,不能记住上一次跑到哪,除非把状态显式放在全局或闭包变量里,那样代码既难维护又容易出并发问题。
def make_list(n):
result = []
i = 0
while i < n:
result.append(i)
i += 1
return result
data = make_list(1000000) # 一次性占用大量内存
三、生成器与普通函数的核心差异
第一,返回对象不同。普通函数return返回具体值(或None),生成器函数调用后立刻返回生成器对象,真实计算推迟。第二,状态保持能力不同。普通函数结束即清空栈帧;生成器在yield处挂起,局部变量、指令指针都保存在生成器对象中,下次next()恢复。第三,内存占用不同:普通函数常需一次性产出完整集合,生成器按需产出,常数为级内存。
第四,控制流语义不同。yield不仅传出值,还将控制权交还调用者;而return意味着终结。生成器还可以用generator.send()接收外部传入值,用throw()向内部抛异常,实现协程式双向通信,这是普通函数难以优雅做到的。下面的例子展示send如何反向影响生成器内部。
def echo():
received = None
while True:
received = yield received
if received == 'stop':
return
g = echo()
next(g) # 预激活
print(g.send(10)) # 输出 10
print(g.send('hi'))# 输出 hi
四、使用场景与注意事项
生成器擅长读取大文件、爬取分页接口、流水线式数据转换等“边产边用”的场景。比如逐行读取数GB日志,用(line for line in open('big.log'))就不会把整个文件读入内存。同时,生成器天然适配for循环与各类消费函数(sum、map等),写出声明式且省资源的代码。
需注意,生成器是一次性消耗品:遍历完后再次next会抛StopIteration,不能像列表那样反复索引。若业务需要多次随机访问,应转为list或换用其他结构。另外,生成器内含有yield时,函数体里的try/finally仍可保证资源释放,但调试挂起状态比普通函数稍复杂,建议在关键节点加日志。总体来看,理解生成器与普通函数的不同,能让你在Python中更从容地平衡内存、性能与代码表达力。
| 对比维度 | 普通函数 | 生成器函数 |
|---|---|---|
| 返回值 | 具体数据或None | 生成器对象 |
| 状态保留 | 调用结束即销毁 | yield挂起保留 |
| 内存特征 | 常需完整集合 | 惰性常数级 |
| 双向通信 | 不支持 | 支持send/throw |