【Python】迭代器和生成器的個人理解,再講一講協程
在認識yield的時候,網上很多文章都是說這個是個生成器,但是我並不知道這個是用來做什麼的,所以概念很快就忘記了,後面讀了幾個文章以後感覺茅塞頓開。我就接介紹一下。
有一篇文章提到,可以把yield看成是生成器的return的一部分,首先一個return的作用是在程式中返回某個值,返回之後程式就不再往下執行了,那麼生成器是是什麼,只有呼叫next()方法的時候該函式才會執行。結合來看,當一個函式帶有yield,它已經不是一個函數了,而是一個生成器,即一個返回迭代器的函式,返回的迭代器有一個next方法,但是每次走一步,到yield停一次,下一次執行到next()時,再從上一次暫停的位置開始,直到執行到下一個yield表示式,將yield關鍵字後的表示式列表返回給呼叫者,並再次暫停。
舉個例子:
def run(): print("starting...") while True: res = yield 1 print("res:",res) g = run() print(next(g)) print("------") print(next(g)) 輸入結果如下: starting... 1 ------ res: None 1
上面的表達有點繞口,但是如果你懂迭代器,那麼在這裡你就懂了。如果不懂,也沒關係,我將一下迭代器。
迭代器的功能主要用於訪問集合元素,迭代器從集合的第一個元素開始訪問,知道所有元素被訪問完結束。向我們的list,tuple,string,dict,都是可以迭代的,對於我們自己實現的型別,如果實現了__iter__()或者__getitem__()方法,那麼該類物件也是可以迭代的。
抽象來看的話,迭代器是一個數據流,對迭代器不斷呼叫next()方法,就可以依次獲取下一個元素,當迭代器沒有元素的時候,呼叫next()會丟擲StopIteration異常。iter()方法則返回一個特殊的迭代物件,當出現StopIteration異常的時候,則識別迭代完成結束。最常見的就是我們的for迴圈:
for i in range li: print(i)
Python 處理for迴圈時,首先會呼叫內建函式 iter(li),它實際上會呼叫 li.__iter__()
,返回 li對應的迭代器。而後,li迴圈會呼叫內建函式next()
,作用在迭代器上,獲取迭代器的下一個元素,並賦值給x
那問題來了,我們知道迭代器的用法,那生成器在什麼地方可以用呢。那就不得不提到協程了。協程也叫微執行緒,舉個例子:
函式的呼叫都是層級呼叫,抽象來看是實現了棧的呼叫,a呼叫b,b呼叫c,那麼c執行完畢返回,再b執行完畢返回,再a執行完畢。這裡的呼叫順序是明確的的。
但是協程不同,雖然它也是子程式,但是在執行的過程中,子程式內部會發生中斷,轉而去執行別的子程式,在適當的時候再跳回來。
那好處在哪裡呢,執行緒的切換是需要開銷的,而子程式的切換由程式自己控制,效能優勢就有了。另外一個是因為只有一個執行緒,不存在讀寫衝突,在控制共享資源的時候不加鎖,優勢就更大了。
正如前面所說的,yield能提供一個函式執行過程中的暫停,這個協程的子程式內部中斷的思想不謀而合,如果使用協程去寫生產者-消費者模型,那麼當生產者生產訊息以後,直接通過yield跳轉到消費者開始執行,待消費者執行完畢以後,切換回生產者繼續生產,效率極高:
import time def consumer(): r = '' while True: n = yield r if not n: return print('[CONSUMER] Consuming %s...' % n) time.sleep(1) r = '200 OK' def produce(c): c.next() n = 0 while n < 5: n = n + 1 print('[PRODUCER] Producing %s...' % n) r = c.send(n) print('[PRODUCER] Consumer return: %s' % r) c.close() if __name__=='__main__': c = consumer() produce(c)