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()
方法匹配起來啦.