Python中的生成器是什么?与普通函数有何不同?

来源:网站主作者:桃乃木香奈头衔:网络博主
导读:本期聚焦于小伙伴创作的《Python中的生成器是什么?与普通函数有何不同?》,敬请观看详情。遇到需要逐个产出海量数据的场景,若一次性放进列表往往直接撑爆内存。Python生成器正是为解决这类问题而设计的惰性求值对象。它遵循迭代器协议,通过yield语句暂停并保留局部状态,下次调用再从断点继续。相较普通函数从头跑到尾、用return交回结果后栈帧即销毁,生成器函数每次触发只推进一小步,既省内存又支持无限序列。理解二者在调用机制、状态保持和性能上的差异,能帮你在处理流式数据或复杂遍历时写出更轻量的代码。

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

Python中的生成器是什么?与普通函数有何不同?

一、生成器的定义与基本形态

生成器本质上是一个实现了迭代器协议(具备__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

generatoriteratoryield修改时间:2026-08-06 19:09:27

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。