yield

yield

yield 用来写生成器(generator) 。

函数里面一旦出现 yield,这个函数就不再是普通函数,调用它不会直接执行函数体,返回一个生成器对象(迭代器)。

return:直接返回结果,函数终止 。

yield:产出一个值,暂停当前函数,保存现场;下次迭代再从暂停位置继续往下跑。

最简单示例

python 复制代码
def gen():
    yield 1
    yield 2
    yield 3

g = gen()   # 不会执行函数内部!拿到生成器对象
print(g)    # <generator object gen at ...>

print(next(g)) # 走到第一个yield,产出1,暂停 →输出1
print(next(g)) # 从暂停处继续,走到yield2,产出2 →输出2
print(next(g)) # 走到yield3,产出3 →输出3
print(next(g)) # 函数跑完,抛出 StopIteration

也可以 for 遍历:

python 复制代码
for v in gen():
    print(v)
# 1  2  3

yield 和 return 的核心对比

  • return val:返回值,函数直接结束,局部状态全部销毁。
  • yield val:产出值,挂起函数,保存局部变量、执行位置;下次next恢复继续执行。
python 复制代码
def f_ret():
    return 1
    return 2 # 永远跑不到

def f_yield():
    yield 1
    yield 2 # 可以跑到

带状态:yield会保存函数局部变量

python 复制代码
def count():
    n = 0
    while n < 3:
        n += 1
        yield n

g = count()
print(next(g)) #1
print(next(g)) #2
print(next(g)) #3

n 的值被保存,不会每次调用重置。普通函数调用一次就销毁局部变量,生成器会保留现场。

关键坑:调用生成器函数不会跑代码

python 复制代码
def gen():
    print("start")
    yield 1

gen()   # 啥都不打印!仅仅拿到生成器
g = gen()
next(g) # 这时才打印 start,产出1

结合之前学的 @contextmanager

python 复制代码
from contextlib import contextmanager

@contextmanager
def demo():
    print("进入,获取资源")
    yield "传给as的值"   # ← with的as拿到这个值
    print("退出,释放资源")

with demo() as v:
    print(v)

执行流程:

  1. 进入with,执行到 yield,产出值给v,函数暂停
  2. 运行with缩进块里面业务代码
  3. 离开with块,恢复生成器,执行yield后面的代码(释放资源)

⚠️ 如果with内部抛异常,异常会在yield位置抛出来。所以 @contextmanager 内部一般要包 try‑finally:

python 复制代码
@contextmanager
def demo():
    print("获取")
    try:
        yield "xxx"
    finally:
        print("释放,一定执行")

如果不写try‑finally,with内部抛异常,yield后面的清理代码不会执行!


yield from(委托生成器)

把另一个生成器的所有值直接产出,不用手写循环:

python 复制代码
def sub():
    yield 10
    yield 20

def main():
    yield from sub()
    yield 30

for x in main():
    print(x) #10,20,30

send():向生成器发送值(进阶)

yield 可以作为表达式接收外部传入的值:

python 复制代码
def gen():
    res = yield "a"
    print(f"收到:{res}")
    yield "b"

g = gen()
print(next(g))      #产出 a
print(g.send(666))  #把666传给 yield表达式,res=666,继续执行产出b

生成器优点:惰性求值

不是一次性把全部数据加载到内存,用一个算一个。处理超大文件、无限数据流非常好用,不占内存。

python 复制代码
# 不会一次性生成100万元素,迭代一个算一个
def big_data(n):
    for i in range(n):
        yield i

for num in big_data(1_000_000):
    if num>10:
        break

极简总结

  1. 函数写了yield →变成生成器函数 ;调用返回生成器对象,不执行函数体。
  2. next() / for迭代,驱动函数运行,遇到yield就产出数据、暂停,保存现场。
  3. yield暂停;return直接结束函数。
  4. @contextmanager就是利用生成器:yield之前=进入上下文;yield之后=退出清理。清理逻辑必须放finally里,否则异常会跳过清理。
  5. 惰性计算,适合大数据,内存友好。
相关推荐
moshuying16 分钟前
块扩散 + GDN 改造:Clover 把「并行生成」塞进了 Agent 的长上下文里
前端·算法
kyriewen18 分钟前
我算了笔账:AI 编程工具一年烧掉两台 MacBook
前端·人工智能·程序员
云和数据.ChenGuang18 分钟前
Python 常见特殊方法详解:__new__、__init__、__del__、__str__、__repr__ 与 __getattribute__
开发语言·人工智能·python·1024程序员节
xyouyuweizhu39 分钟前
字节流与字符流:Java IO 的两条主线该怎么选
java·开发语言·python
迪菲赫尔曼43 分钟前
【基础篇 04】第一次使用终端:命令在哪里输入,电脑现在又“站”在哪里?
python·yolo·目标检测
moshuying1 小时前
扩散模型开始写代码、开网页了:350 亿参数,速度比 Jev 快一倍
前端·算法
qianbo_insist1 小时前
# 在 Qwen3.5-2B 上把 GDN-1 无损换成 GDN-2:一次完整的架构手术实验(负结果)
人工智能·pytorch·python·架构
qq_401700411 小时前
100 个 IoT 小项目,给 ESP32 入门补上实操
前端·网络·stm32
Random Glow2 小时前
二级索引与回表:一次回表到底多贵
数据库·mysql·性能优化·innodb·索引