python-復(fù)盤-徹底理解生成器yield中next/sent函數(shù)區(qū)別

yield與生成器

def func(n):  
    for i in range(0, n):  
        print('func: ', i)  
        yield i  
  
f = func(10) 

print(func(10))     # 生成器是無法執(zhí)行的
<generator object func at 0x0000004B57353A98>

如何調(diào)用生成器?

import time  
def func(n):  
    for i in range(0, n):  
        print('func: ', i)  
        yield i  
  
f = func(10)  
while True:  
    print(next(f))  
    time.sleep(1)  

結(jié)果如下:

func:  0  
0  
func:  1  
1  
func:  2  
2  
...........
func:  8  
8  
func:  9  
9  
Traceback (most recent call last):  
  File "C:/Users/mingC/PycharmProjects/pro_test/Demo/Demo4.py", line 9, in <module>  
    print(next(f))  
StopIteration  

再看一個例子

In [62]: def play():
    ...:     try:
    ...:         yield 1    #  程序每執(zhí)行一次暫停,下次從斷點處執(zhí)行
    ...:         yield 2
    ...:         yield 3
    ...:     finally:
    ...:         yield 0

結(jié)果:

In [68]: for v in play():
    ...:     print (v)
    ...:
1
2
3
0

生成器函數(shù)在每次暫停執(zhí)行時,函數(shù)體內(nèi)的所有變量都將被封存(freeze)在生成器中,并將在恢復(fù)執(zhí)行時還原,并且類似于閉包,即使是同一個生成器函數(shù)返回的生成器,封存的變量也是互相獨立的。


send

send是除next外另一個恢復(fù)生成器的方法。Python 2.5中,yield語句變成了yield表達(dá)式,這意味著yield現(xiàn)在可以有一個值,而這個值就是在生成器的send方法被調(diào)用從而恢復(fù)執(zhí)行時,調(diào)用send方法的參數(shù)。

>>>def repeater():
...   n = 0  
...   while True:  
...     n = (yield n)

>>> r = repeater() 
>>>r.next() 
0                      # 結(jié)果
>>>r.send(10) 
10 



再看一個例子(經(jīng)典):

import time  
def func(n):  
    for i in range(0, n):    # 在yield中我們知道程序遇到y(tǒng)ield就會暫停,return是徹底停止。這里的 for 循環(huán)相當(dāng)于形成一個貪吃蛇??的模型,不斷從下方的暫停點和啟動點循環(huán),每次到暫停點,i會+1,且值會給到send,若上一個啟動點是send啟動的話。
        arg = yield i       # yield i 是停止代碼執(zhí)行暫停點,arg = yield 是代碼啟動點,注意理解 ! 
        print('func:', arg)       # 回到y(tǒng)ield i 這個終止點的時候,此時已經(jīng)算是又一次的循環(huán),因為開始從arg = yield時并未循環(huán),所以此時應(yīng)該 i +1 ,所以結(jié)果里的main2 相對main1 要大一個 1
  
f = func(10)  
while True:  
    print('main1:', next(f))     # next每次從arg = yield的啟動點開始,由于它不像send帶value,所以,此時arg是None,即不存在。所以每次打印都是None
    print('main2:', f.send(100))  
    time.sleep(1)

輸出結(jié)果:

main1: 0  
func: 100  
main2: 1  
func: None  
main1: 2  
func: 100  
main2: 3  
func: None  
main1: 4  
func: 100  
main2: 5  
func: None  
main1: 6  
func: 100  
main2: 7  
func: None  
main1: 8  
func: 100  
main2: 9  
Traceback (most recent call last):    
func: None     #  9之后,next(f)它是從arg = yield開始執(zhí)行的,所以雖然range已經(jīng)結(jié)束,但它還是頑強的把None給打印出來,再回到y(tǒng)ield i 的時候才徹底結(jié)束。
  File "C:/Users/mingC/PycharmProjects/pro_test/Demo/Demo4.py", line 9, in <module>  
    print('main:', next(f))  
StopIteration  


再來看一段yield更復(fù)雜的用法

>>> def echo(value=None):
...   while 1:
...     value =  yield  value
...     print("The value is", value)
...     if value:
...       value += 1
...
>>> g = echo(1)
>>> next(g)   #  這是第一次啟動生成器,所以按順序執(zhí)行,到y(tǒng)ield value這句時程序停止執(zhí)行,但此時yield會把停止時產(chǎn)生的值給生成器echo,即對應(yīng)next(g)的值,所以第一次是1
1    # 注:value = yield value可以看成 value = yield 和 yield value,左邊是啟動點,右邊是暫停點
>>> g.send(2)   # 第二次執(zhí)行,send 把2給了啟動點 value = yield,所以有了value is 2,接著呢,+1變成3,此時這個值要給到循環(huán)給到暫停點yield value,程序暫停,此時yield value的結(jié)果是3,3要回傳給g.send(2)
The value is 2     # send的特點是給一個value值,這個值等同于yield后的結(jié)果值,也就是啟動點的值,相當(dāng)于發(fā)了一個消息。轉(zhuǎn)一圈了,等到暫停點 yield value時,send要接收這個yield value的結(jié)果。
3
>>> g.send(5)
The value is 5
6
>>> next(g)
The value is None

解釋,口語化講解參考代碼后備注:
上述代碼既有yield value的形式,又有value = yield形式,看起來有點復(fù)雜.但以yield分離代碼進(jìn)行解讀,就不太難了.第一次調(diào)用next()方法,執(zhí)行到yield value表達(dá)式,保存上下文環(huán)境暫停返回1.第二次調(diào)用send(value)方法,從value = yield開始,打印,再次遇到y(tǒng)ield value暫停返回.后續(xù)的調(diào)用send(value)next()都不外如是.

但是,這里就引出了另一個問題,yield作為一個暫停恢復(fù)的點,代碼從yield處恢復(fù),又在下一個yield處暫停.可見,在一次next()(非首次)或send(value)調(diào)用過程中,實際上存在2個yield,一個作為恢復(fù)點的yield與一個作為暫停點的yield.因此,也就有2個yield表達(dá)式.send(value)方法是將值傳給恢復(fù)點yield;調(diào)用next()表達(dá)式的值時,其恢復(fù)點yield的值總是為None,而將暫停點的yield表達(dá)式的值返回.為方便記憶,你可以將此處的恢復(fù)點記作當(dāng)前的(current),而將暫停點記作下一次的(next),這樣就與next()方法匹配起來啦.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容