yield & send in python
Table of Contents
Python 中 yield 和 send() 的执行过程详解
1. 示例代码
先看这段代码:
def consumer(): r = '' while True: n = yield r if not n: return print('[CONSUMER] Consuming %s...' % n) r = '200 OK'
def produce(c): c.send(None) n = 0 while n < 5: n = n + 1 print('[PRODUCER] Producing %s...' % n) r = c.send(n) print('[PRODUCER] Consumer return: %s' % r) c.close()
c = consumer()produce(c)这段代码演示的是一个简单的 生产者 - 消费者协程模型。
其中:
produce()是生产者,负责生产数据;consumer()是消费者,负责消费数据;yield用来暂停消费者;send()用来恢复消费者,并向消费者传入数据。
2. yield 的基本作用
普通函数遇到 return 就结束了。
例如:
def f(): return 1
x = f()print(x)输出:
1但是,如果一个函数里出现了 yield,这个函数就不再是普通函数,而是一个 生成器函数。
例如:
def g(): yield 1 yield 2 yield 3调用它时:
gen = g()函数体不会立刻执行,而是返回一个生成器对象。
只有调用:
next(gen)或者:
gen.send(None)时,函数才会开始执行。
3. yield x 的含义
在生成器中:
yield x表示:
暂停当前函数,并把
x返回给外部。
例如:
def g(): yield 100
gen = g()
result = next(gen)print(result)输出:
100也就是说,yield 后面的值会成为外部调用 next() 或 send() 的返回值。
4. send() 的作用
生成器除了可以用 next() 恢复执行,还可以用 send() 恢复执行。
区别是:
next(gen)只能恢复生成器执行。
而:
gen.send(value)不仅可以恢复生成器执行,还可以把 value 发送到生成器内部。
5. n = yield r 的真正含义
你这段代码里最关键的一句是:
n = yield r这句代码同时包含两个方向:
第一:向外返回 r
yield r表示把 r 返回给外部。
所以外面这句:
result = c.send(value)拿到的返回值,就是 yield r 中的 r。
第二:接收外部 send 进来的值
n = yield r当外部下一次执行:
c.send(value)时,value 会被传回生成器内部,作为 yield r 这个表达式的结果,然后赋值给 n。
也就是说:
n = yield r可以理解成:
# 1. 把 r 返回给外部# 2. 暂停函数# 3. 等外部 send(value)# 4. 把 value 赋值给 n所以:
n = yield r里面:
r是返回给外部的值;n是接收外部传进来的值。
6. send() 的返回值是不是一直等于 yield 后面的值?
是的,但要说准确一点:
每一次
send()的返回值,等于生成器恢复执行后遇到的下一个yield后面的值。
也就是说:
yield x并不是规定以后 send() 永远返回 x。
而是说:
当前这一次暂停时,生成器把此时的
x返回给外部。
如果 x 改变了,那么下一次 send() 的返回值也会改变。
例如:
def g(): x = 1 while True: n = yield x x += 1
gen = g()
print(gen.send(None)) # 1print(gen.send('a')) # 2print(gen.send('b')) # 3print(gen.send('c')) # 4虽然每次都是:
n = yield x但 x 的值一直在变,所以 send() 的返回值也在变。
7. 为什么第一次要 c.send(None)?
在你的代码中:
c = consumer()这一步只是创建了生成器对象。
此时 consumer() 内部代码还没有开始执行。
也就是说,这时候还没有执行到:
n = yield r生成器还没有停在任何一个 yield 上。
所以第一次启动生成器时,必须先用:
c.send(None)或者:
next(c)它们的作用类似,都是让生成器运行到第一个 yield 并暂停。
如果第一次直接写:
c.send(1)会报错:
TypeError: can't send non-None value to a just-started generator原因是:
生成器还没有运行到第一个
yield,所以没有地方接收你 send 进去的值。
8. 第一次 c.send(None) 做了什么?
看 consumer():
def consumer(): r = '' while True: n = yield r if not n: return print('[CONSUMER] Consuming %s...' % n) r = '200 OK'第一次执行:
c.send(None)生成器开始运行:
r = ''然后进入循环:
while True:执行到:
n = yield r此时 r 是空字符串:
r = ''所以第一次 yield r 返回的是:
''但是在你的代码中:
c.send(None)没有用变量接收这个返回值,所以这个空字符串被忽略了。
如果写成:
first = c.send(None)print(first)那么会打印一个空字符串。
9. 第一次正式发送数据:c.send(1)
生产者代码中:
n = n + 1print('[PRODUCER] Producing %s...' % n)r = c.send(n)第一次循环时:
n = 1所以执行:
r = c.send(1)这时候消费者之前停在这里:
n = yield r现在外部发送了 1 进来。
于是:
n = yield r中的 yield r 这个表达式的结果就是 1。
所以消费者内部变成:
n = 1然后继续执行:
if not n: return因为 n = 1,所以不会 return。
接着执行:
print('[CONSUMER] Consuming %s...' % n)打印:
[CONSUMER] Consuming 1...然后:
r = '200 OK'接着循环继续,又回到:
n = yield r此时 r 已经变成了:
'200 OK'所以这次 yield r 会把:
'200 OK'返回给外面的:
r = c.send(1)因此生产者中的 r 得到的值就是:
'200 OK'所以打印:
[PRODUCER] Consumer return: 200 OK10. 完整执行流程
创建生成器
c = consumer()此时只是创建生成器,consumer() 内部代码还没有执行。
启动生成器
c.send(None)消费者执行到:
n = yield r此时:
r = ''所以第一次返回空字符串,并暂停。
生产者生产 1
print('[PRODUCER] Producing 1...')r = c.send(1)输出:
[PRODUCER] Producing 1...消费者收到:
n = 1然后消费数据:
[CONSUMER] Consuming 1...消费完成后:
r = '200 OK'然后再次执行到:
n = yield r把 '200 OK' 返回给生产者。
生产者拿到返回值后打印:
[PRODUCER] Consumer return: 200 OK生产者生产 2
print('[PRODUCER] Producing 2...')r = c.send(2)输出:
[PRODUCER] Producing 2...消费者收到:
n = 2打印:
[CONSUMER] Consuming 2...然后再次返回:
'200 OK'生产者打印:
[PRODUCER] Consumer return: 200 OK后续 3、4、5 也是一样
每次流程都是:
生产者生产一个数字send 给消费者消费者消费这个数字消费者把 r 设置为 '200 OK'下一次 yield r生产者拿到 '200 OK'11. 最终输出结果
这段代码最终大致输出:
[PRODUCER] Producing 1...[CONSUMER] Consuming 1...[PRODUCER] Consumer return: 200 OK[PRODUCER] Producing 2...[CONSUMER] Consuming 2...[PRODUCER] Consumer return: 200 OK[PRODUCER] Producing 3...[CONSUMER] Consuming 3...[PRODUCER] Consumer return: 200 OK[PRODUCER] Producing 4...[CONSUMER] Consuming 4...[PRODUCER] Consumer return: 200 OK[PRODUCER] Producing 5...[CONSUMER] Consuming 5...[PRODUCER] Consumer return: 200 OK注意:第一次 c.send(None) 返回的是空字符串,但是代码没有打印它。
12. 用表格理解执行过程
| 步骤 | 外部调用 | 发送给消费者的值 | 消费者内部 n | 消费者 yield 返回值 | 外部收到的返回值 |
|---|---|---|---|---|---|
| 1 | c.send(None) | None | 暂未赋值 | '' | '',但被忽略 |
| 2 | c.send(1) | 1 | 1 | '200 OK' | '200 OK' |
| 3 | c.send(2) | 2 | 2 | '200 OK' | '200 OK' |
| 4 | c.send(3) | 3 | 3 | '200 OK' | '200 OK' |
| 5 | c.send(4) | 4 | 4 | '200 OK' | '200 OK' |
| 6 | c.send(5) | 5 | 5 | '200 OK' | '200 OK' |
13. 关键点总结
yield r
表示:
把 r 返回给外部,然后暂停函数。c.send(n)
表示:
把 n 发送进生成器,并恢复生成器继续执行。r = c.send(n)
表示:
把 n 发给生成器;生成器运行到下一次 yield;yield 后面的值作为 c.send(n) 的返回值;赋值给外部变量 r。n = yield r
这是双向通信:
r:返回给外部;n:接收外部 send 进来的值。所以这句可以记成:
外面拿到 r;里面收到 n。14. 为什么后面的返回值一直是 200 OK?
因为消费者每次消费完数据后,都会执行:
r = '200 OK'然后循环回到:
n = yield r此时 yield r 中的 r 就是:
'200 OK'所以外部:
r = c.send(n)每次拿到的返回值都是:
'200 OK'但是它不是因为 send() 固定返回 '200 OK'。
而是因为消费者内部每次都把 r 设置成了 '200 OK'。
如果你改成:
r = f'{n} consumed'那么返回值就会变化。
例如:
def consumer(): r = '' while True: n = yield r if not n: return print('[CONSUMER] Consuming %s...' % n) r = f'{n} consumed'那么输出会变成:
[PRODUCER] Producing 1...[CONSUMER] Consuming 1...[PRODUCER] Consumer return: 1 consumed[PRODUCER] Producing 2...[CONSUMER] Consuming 2...[PRODUCER] Consumer return: 2 consumed[PRODUCER] Producing 3...[CONSUMER] Consuming 3...[PRODUCER] Consumer return: 3 consumed15. 如果发送的是 0 会怎么样?
消费者中有这段代码:
if not n: return在 Python 中,下面这些值都会被认为是 False:
0None''False[]{}所以如果执行:
c.send(0)那么:
n = 0接着:
if not n: return条件成立,消费者结束。
生成器结束后,会抛出:
StopIteration所以:
c.send(0)不会正常返回 '200 OK',而是终止生成器。
16. close() 的作用
生产者最后调用:
c.close()表示关闭生成器。
关闭之后,生成器不能再继续执行。
如果关闭后再调用:
c.send(1)会抛出:
StopIteration17. 最重要的一句话
这句代码:
n = yield r可以记成:
yield 后面的 r,是返回给外面的值;send 进来的值,会赋给里面的 n。所以:
r = c.send(n)可以理解成:
外部把 n 发送进去;内部接收为 n;内部处理完后 yield r;外部拿到 r。这就是 yield + send() 实现协程通信的核心。
评论 (0)
请先登录后再发表评论