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)
执行流程:
- 进入with,执行到
yield,产出值给v,函数暂停 - 运行with缩进块里面业务代码
- 离开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
极简总结
- 函数写了
yield→变成生成器函数 ;调用返回生成器对象,不执行函数体。 next()/ for迭代,驱动函数运行,遇到yield就产出数据、暂停,保存现场。yield暂停;return直接结束函数。@contextmanager就是利用生成器:yield之前=进入上下文;yield之后=退出清理。清理逻辑必须放finally里,否则异常会跳过清理。- 惰性计算,适合大数据,内存友好。