yield筆記

看到協(xié)程時(shí)對(duì)yield的用法總是理解不夠透徹,因此做一些小筆記,方便日后查看。
此處以一個(gè)小例子來說明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

#相當(dāng)于
f = fib(5)
for j in range(5):
    print(next(f),end=' ')

在上面的例子中,函數(shù)fib(n)相當(dāng)于一個(gè)生成器,for循環(huán)每一次調(diào)用相當(dāng)于執(zhí)行一次next(),最后調(diào)用完會(huì)遇到StopIteration退出for循環(huán)。

2、send是什么?

想要了解send()是什么,就不得不先理解yield表達(dá)式了。yield表達(dá)式可表示為[res =] yield [expression],從某種程度來說,方括號(hào)中的值都是可以省略的,例如若將fib(n)函數(shù)的yield n改成yield也不會(huì)報(bào)錯(cuò),只是這樣fib(5)這個(gè)生成器就會(huì)返回5個(gè)None了,而將yield n改成 s = yield n結(jié)果則不會(huì)變,只是此時(shí)可以通過sendyield表達(dá)式傳入數(shù)值,這個(gè)數(shù)值即賦值給了s??纯创a更加清晰:

# 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表達(dá)式后對(duì)生成器沒有改變,那么他有什么作用呢?要想yield表達(dá)式發(fā)揮作用,就必須使用send對(duì)其進(jìn)行傳值(賦值),利用傳入的值可以來實(shí)現(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)   #這一步是必須的,此處相當(dāng)于send(None),在fib函數(shù)中此時(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表達(dá)式的左邊的now變量了。另外值得注意的一點(diǎn)是,在使用send之前必須先調(diào)用一次next(),此處的next相當(dāng)于send(None)。
yield表達(dá)式的執(zhí)行順序是先yield產(chǎn)生值,然后掛起等待send傳入值。也因此輸出的結(jié)果是先輸出fib序列,然后在輸出傳入值相關(guān)的信息。
下面的例子更好的說明了執(zhí)行步驟,為了更好的說明執(zhí)行順序,此處將fib序列的第一個(gè)值改成了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
#此時(shí)僅執(zhí)行了一次send,輸出如下
3
2017-12-21 21:23:06.106728
5

上面的兩個(gè)不同的fib生成結(jié)果中第一個(gè)3是在預(yù)激活協(xié)程時(shí)yield產(chǎn)生的,在yield表達(dá)式右邊產(chǎn)生值后,便會(huì)掛起等待傳入?yún)?shù)并賦值給左側(cè)的變量now。隨后send將時(shí)間傳入賦值給了yield 表達(dá)式左邊的nownow被賦值后會(huì)一直執(zhí)行到再次yield b生成5,這也是為什么下面的res是5。此時(shí)yield 表達(dá)式又再次執(zhí)行到了yield并掛起等待給now賦值(send)的時(shí)候。如此循環(huán),直到yield表達(dá)式右側(cè)(這里的右側(cè)依舊是一個(gè)生成器)的值耗盡,這是再次send時(shí)會(huì)引發(fā)生StopIteration。

3、yield from 是何方神圣?

說完了send,yield from又是用來干什么的呢?下面這個(gè)例子也許可以對(duì)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的作用僅僅如此嗎?
你見過有返回值的生成器嗎?下面這個(gè)生成器在終止時(shí)(觸發(fā)StopIteration)會(huì)返回一個(gè)值。

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,而且這個(gè)返回值并非yield的值,而是子生成器函數(shù)func的返回值
其次,從輸出結(jié)果可以看出,send發(fā)送的值都傳到了子生成器中,也即是從委派生成器delegate傳到了yield from表達(dá)式中的子生成器func中,這也是輸出結(jié)果index 10 ... index 14的由來。
最后,委派生成器向子生成器send發(fā)送值后,自身會(huì)被掛起,直到子生成器函數(shù)func觸發(fā)終止異常(StopIteration)返回值,這個(gè)返回值賦值給yield from表達(dá)式左邊的res,然后委派生成器就會(huì)繼續(xù)執(zhí)行,這也是為什么,res:111會(huì)在最后輸出。當(dāng)我們將while i<15改成while i<12后會(huì)看到輸出結(jié)果沒有res輸出,這是因?yàn)樯善鬟€沒有迭代完(while index<5這里需要send5次才會(huì)觸發(fā)異常返回值),還在等待send發(fā)送值。

現(xiàn)在,讓我們梳理下上面代碼的執(zhí)行順序:
①預(yù)激活子生成器func,此時(shí)子生成器在等待傳值;
②向委派生成器delegate傳值(send);
③委派生成器通過yield from表達(dá)式向子生成器func中傳(send)值;
④子生成器收到傳入的值i后,便會(huì)向后執(zhí)行print('s: ', s),index+=1yield產(chǎn)生值(雖然此處沒有生成任何值),最后又回到繼續(xù)等待傳值(send)的狀態(tài);
⑤代碼中沒有⑤因?yàn)棰荽碇h(huán)傳值這個(gè)過程;
終止生成器,這一步非常關(guān)鍵,因?yàn)槊總魅?send)一個(gè)值后都會(huì)執(zhí)行index+=1,回到等待傳值得狀態(tài)。因此第一次傳值后index=1(需要注意的是預(yù)激活時(shí)index為0),第四次傳值(send(13))后,此時(shí)index=4,當(dāng)?shù)谖宕蝹髦禃r(shí)index=5此時(shí)會(huì)觸發(fā)異常退出while循環(huán),使得子生成器func返回res值,func返回的值又通過yield from表達(dá)式賦值給委派生成器的res,res收到值后委派生成器終于不再掛起,向下執(zhí)行,print(res)。完結(jié)撒花。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 從語法上來看,協(xié)程和生成器類似,都是定義體中包含yield關(guān)鍵字的函數(shù)。yield在協(xié)程中的用法:在協(xié)程中yiel...
    JokerW閱讀 1,863評(píng)論 0 0
  • 1.1==,is的使用 ·is是比較兩個(gè)引用是否指向了同一個(gè)對(duì)象(引用比較)。 ·==是比較兩個(gè)對(duì)象是否相等。 1...
    TENG書閱讀 790評(píng)論 0 0
  • 第五章 序列和協(xié)程 來源:Chapter 5: Sequences and Coroutines 譯者:飛龍 協(xié)議...
    布客飛龍閱讀 753評(píng)論 0 37
  • 你不知道JS:異步 第四章:生成器(Generators) 在第二章,我們明確了采用回調(diào)表示異步流的兩個(gè)關(guān)鍵缺點(diǎn):...
    purple_force閱讀 1,043評(píng)論 0 2
  • 文/南下溪 海子說:“我們最終都要遠(yuǎn)行,都要與稚嫩的自己告別通向苦行之路。”有人選擇按部就班地承受生活的煎熬,有人...
    南下溪閱讀 9,594評(píng)論 196 669

友情鏈接更多精彩內(nèi)容