python中的yield生成器詳解
#原創,轉載請先聯系
在學習生成器之前,必須先了解一下叠代器。因為生成器就是一種特殊的叠代器,而且生成器用起來更加優雅。
叠代器的詳解可以參考我的另一篇博文:https://www.cnblogs.com/chichung/p/9537969.html
先說一種比較簡單的生成器,通過例子慢慢來體會什麽是生成器。
# 列表生成式 L = [x for x in range(5)] print(L) #簡單的生成器 G = (x for x in range(5)) # G就是一個生成器,也是一個叠代器,叠代器也是可叠代對象,所以這個G也可以說是可叠代對象 print(next(G)) print(next(G))print(next(G)) print(next(G)) print(next(G)) 輸出: [0, 1, 2, 3, 4] 0 1 2 3 4
把列表生成器的[]改為()就變成一個簡單的生成器。由上面的例子,我們大概可以知道,生成器就是一個叠代器,把數據一個一個拿出來,可以減少內存的負擔。
那麽,yield又是一個什麽東西呢?為什麽說他優雅呢?
當我們寫的代碼輸出的結果,想一個一個出來。有兩種常用的方法:
方法1.我們可以創建一個叠代器類,然後把代碼寫進類裏,用類來創建一個可叠代對象,然後用next()函數一個一個把結果叠代出來。
方法2.我們可以用代碼函數的合適位置加上yield,這時候這個函數就變成一個生成器了,不需要再創建一個叠代器類,不需要再寫__iter__,__next__方法了。這樣一來不是很方便,很優雅嗎?哈哈哈哈~
口說無憑,下面我們2個方法都做一下,讓你們體會一下:
我們做一個斐波那契的數列生成器。斐波那契數列的第一個數是0,第二個數是1,第三個數是第一、二個數相加,第四個數是第二、三個數相加......
方法1:
class FeiboIterator(): def __init__(self): self.a = 0 self.b = 1 def __iter__(self): return self def __next__(self): num = self.a self.a,self.b= self.b,self.a+self.b return num iterator = FeiboIterator() print(next(iterator)) print(next(iterator)) print(next(iterator)) print(next(iterator)) print(next(iterator)) print(next(iterator)) print(next(iterator)) print(next(iterator)) 輸出:
0 1 2 3 5 8 13
是不是很麻煩?又要初始化,又要寫__iter__和__next__魔方方法。
方法2:
def feibo(): a = 0 b = 1 while True: yield a # 假如yield後面緊接著一個數據,就會把數據返回,作為next()函數或者for ...in...叠代出的下一個值 a,b = b,a+b generator = feibo() print(next(generator)) print(next(generator)) print(next(generator)) print(next(generator)) print(next(generator)) print(next(generator)) print(next(generator)) print(next(generator)) 輸出: 0 1 1 2 3 5 8 13
看!只有6行代碼,是不是很elegant?關於這個程序是怎麽運行的?yield是怎麽運作的?我們等下就講,現在需要註意幾點:
1.上面代碼的紅色字那裏!假如yield後面緊接著一個數據,就會把數據返回,作為next()函數或者for ...in...叠代出的下一個值。
2.假如函數中有yield,就不再是函數。而是一個能返回生成器的函數!註意!是返回,這個函數並不是一個生成器。
3.拿到函數的生成器後,可以和叠代器一樣,用next()函數獲得下一個值。
好了,該來理解一下yield是怎麽運作的了!
1.第一次喚醒生成器時,是從函數的起始位置開始,直到遇到yield,就會暫停函數,掛起函數。
2.第二次喚醒生成器時,是從yield斷點處開始,直到又遇到yield。
3.當生成器已經沒有yield,再使用next,則拋StopIteration異常。
然後,我們來理一下上面用yield寫的代碼。
第一次用next()喚醒生成器時,從函數的開頭開始運行,遇到yield a,返回a,然後暫停函數,並記住函數是運行到這個位置的。
第二次喚醒生成器,從yield a斷點處開始,然後a,b開始賦值,while True循環又遇見yield a,返回a,然後暫停函數,並記住函數是運行到這個位置的。
下面喚醒多少次都是這個道理,但是由於這個函數是死循環,所以不會沒有yield,也就不會拋出StopIteration異常。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
其實yield還能接受值,用send方法進行傳入。代碼體會一下:
def gg(): i = 1 while True: recv = yield i print("接收到一個值:",recv) i += 1 generator = gg() print(next(generator)) print(generator.send("456")) print(generator.send("789")) 輸出: 1 接收到一個值: 456 2 接收到一個值: 789 3
實現過程和上面的例子一樣。
要懂得的是,yield = a,會返回a。
b = yield,會把yield接收的值賦值給b。
python中的yield生成器詳解