看到協(xié)程時對yield的用法總是理解不夠透徹,因此做一些小筆記横浑,方便日后查看剔桨。
此處以一個小例子來說明send到底是干嘛用的,例子來自Python協(xié)程:從yield/send到async/await:
1徙融、起源:簡單的yield生成器
def fib(n):
index = a = 0
b = 1
while index<n:
yield b
a, b = b, a+b
index += 1
#簡單調(diào)用:
for i in fib(5):
print(i,end=' ',sep=',')
# 1 1 2 3 5
#相當于
f = fib(5)
for j in range(5):
print(next(f),end=' ')
在上面的例子中洒缀,函數(shù)fib(n)
相當于一個生成器,for
循環(huán)每一次調(diào)用相當于執(zhí)行一次next()
,最后調(diào)用完會遇到StopIteration
退出for
循環(huán)欺冀。
2树绩、send是什么?
想要了解send()
是什么隐轩,就不得不先理解yield表達式
了饺饭。yield表達式可表示為[res =] yield [expression]
,從某種程度來說,方括號中的值都是可以省略的职车,例如若將fib(n)
函數(shù)的yield n
改成yield
也不會報錯瘫俊,只是這樣fib(5)
這個生成器就會返回5個None了鹊杖,而將yield n
改成 s = yield n
結(jié)果則不會變,只是此時可以通過send
向yield表達式傳入數(shù)值
扛芽,這個數(shù)值即賦值給了s
骂蓖。看看代碼更加清晰:
# yield b ==> yield
def fib(n):
index = a = 0
b = 1
while index<n:
yield
a, b = b, a+b
index += 1
for i in fib(5):
print(i,end=' ')
# None None None None None
# yield b ==> s=yield b
def fib(n):
index = a = 0
b = 1
while index<n:
s = yield b
a, b = b, a+b
index += 1
# 1 1 2 3 5
既然使用了yield表達式
后對生成器沒有改變川尖,那么他有什么作用呢登下?要想yield表達式發(fā)揮作用,就必須使用send對其進行傳值(賦值)叮喳,利用傳入的值可以來實現(xiàn)一些有用的功能庐船,例如下面簡單的記錄一下日志信息:
import datetime
import time
import random
def fib(n):
index = a = 0
b = 1
while index < n:
now = yield b
print(now)
a, b = b, a+b
index += 1
f = fib(5)
res = next(f) #這一步是必須的,此處相當于send(None)嘲更,在fib函數(shù)中此時執(zhí)行到y(tǒng)ield產(chǎn)出值b=1(也即res等于1),并掛起等待send傳入值
while True:
try:
print(res)
time.sleep(random.random())
res = f.send(datetime.datetime.now())
except:
print('over')
break
#輸出
1
2017-12-21 14:15:49.296765
1
2017-12-21 14:15:49.583339
2
2017-12-21 14:15:50.492255
3
2017-12-21 14:15:51.006442
5
2017-12-21 14:15:51.113537
over
從上面可以看出,send
發(fā)送的值都賦值給了yield表達式
的左邊的now
變量了揩瞪。另外值得注意的一點是赋朦,在使用send
之前必須先調(diào)用一次next()
,此處的next
相當于send(None)
。
yield表達式
的執(zhí)行順序是先yield
產(chǎn)生值李破,然后掛起等待send
傳入值宠哄。也因此輸出的結(jié)果是先輸出fib序列,然后在輸出傳入值相關(guān)的信息嗤攻。
下面的例子更好的說明了執(zhí)行步驟毛嫉,為了更好的說明執(zhí)行順序,此處將fib序列的第一個值改成了2:
import datetime
import time
import random
def fib(n):
index = 0
a = 2
b = 3
while index < n:
now = yield b
print(now)
a, b = b, a+b
index += 1
f = fib(5)
res = next(f)
n = 1
while n < 2:
try:
print(res)
time.sleep(random.random())
res = f.send(datetime.datetime.now())
print(res)
except:
print('over')
break
n += 1
#此時僅執(zhí)行了一次send妇菱,輸出如下
3
2017-12-21 21:23:06.106728
5
上面的兩個不同的fib生成結(jié)果中第一個3是在預(yù)激活協(xié)程時yield
產(chǎn)生的,在yield表達式
右邊產(chǎn)生值后承粤,便會掛起等待傳入?yún)?shù)并賦值給左側(cè)的變量now
。隨后send
將時間傳入賦值給了yield 表達式
左邊的now
闯团,now
被賦值后會一直執(zhí)行到再次yield b
生成5辛臊,這也是為什么下面的res是5。此時yield 表達式
又再次執(zhí)行到了yield
并掛起等待給now
賦值(send
)的時候房交。如此循環(huán)彻舰,直到yield表達式
右側(cè)(這里的右側(cè)依舊是一個生成器)的值耗盡候味,這是再次send
時會引發(fā)生StopIteration
。
3、yield from 是何方神圣?
說完了send
,yield from
又是用來干什么的呢?下面這個例子也許可以對yield from的用法做一些最簡單的說明:
def f1():
for i in range(5):
yield i
for j in 'abc':
yield j
def f2():
yield from f1()
# 下面兩種調(diào)用生成器的結(jié)果是一致的
for i in f1():
print(i,end=' ')
for j in f2():
print(j,end=' ')
# 0 1 2 3 4 a b c
但是yield from
的作用僅僅如此嗎眉枕?
你見過有返回值的生成器嗎姥宝?下面這個生成器在終止時(觸發(fā)StopIteration
)會返回一個值碳蛋。
def func():
index = 0
res = 111
while index < 5:
s = yield # ④
print('s: ', s)
index += 1
return res
def delegate():
res = yield from func() # ③ ##⑥
print('res: ', res)
f = delegate()
f.send(None) # ①
i = 10
while i < 15:
try:
f.send(i) # ② 此處send的值發(fā)送到了子生成器func()中
except:
pass
i += 1
#輸出
s: 10
s: 11
s: 12
s: 13
s: 14
res: 111
從輸出結(jié)果可以看出3件事:
首先,委派生成器delegate
的確從yield from
中收到了返回值——res=111
,而且這個返回值并非yield的值比肄,而是子生成器函數(shù)func的返回值
掀亥。
其次撮竿,從輸出結(jié)果可以看出,send
發(fā)送的值都傳到了子生成器中惨驶,也即是從委派生成器delegate
傳到了yield from
表達式中的子生成器func
中续扔,這也是輸出結(jié)果index 10 ... index 14
的由來识脆。
最后艘包,委派生成器向子生成器send
發(fā)送值后胡诗,自身會被掛起,直到子生成器函數(shù)func觸發(fā)終止異常(StopIteration
)返回值震庭,這個返回值賦值給yield from
表達式左邊的res
瑰抵,然后委派生成器就會繼續(xù)執(zhí)行,這也是為什么器联,res:111
會在最后輸出二汛。當我們將while i<15
改成while i<12
后會看到輸出結(jié)果沒有res
輸出,這是因為生成器還沒有迭代完(while index<5
這里需要send
5次才會觸發(fā)異常返回值)拨拓,還在等待send
發(fā)送值肴颊。
現(xiàn)在,讓我們梳理下上面代碼的執(zhí)行順序:
①預(yù)激活子生成器func
渣磷,此時子生成器在等待傳值婿着;
②向委派生成器delegate
傳值(send
);
③委派生成器通過yield from
表達式向子生成器func
中傳(send
)值醋界;
④子生成器收到傳入的值i
后竟宋,便會向后執(zhí)行print('s: ', s)
,index+=1
并yield
產(chǎn)生值(雖然此處沒有生成任何值)形纺,最后又回到繼續(xù)等待傳值(send
)的狀態(tài)丘侠;
⑤代碼中沒有⑤因為⑤代表著循環(huán)傳值這個過程;
⑥終止生成器
逐样,這一步非常關(guān)鍵蜗字,因為每傳入(send
)一個值后都會執(zhí)行index+=1
,回到等待傳值得狀態(tài)脂新。因此第一次傳值后index=1
(需要注意的是預(yù)激活時index為0)秽澳,第四次傳值(send(13)
)后,此時index=4
戏羽,當?shù)谖宕蝹髦禃rindex=5
此時會觸發(fā)異常退出while
循環(huán)担神,使得子生成器func
返回res
值,func
返回的值又通過yield from
表達式賦值給委派生成器的res
始花,res
收到值后委派生成器終于不再掛起妄讯,向下執(zhí)行孩锡,print(res)
。完結(jié)撒花亥贸。