Python並發編程之從生成器使用入門協程(七)
來自專欄 Python進階開發之路
大家好,並發編程
進入第七篇。
從今天開始,我們將開始進入Python的難點,那就是協程
。
為了寫明白協程的知識點,我查閱了網上的很多相關資料。發現很難有一個講得系統,講得全面的文章,導致我們在學習的時候,往往半知半解,學完還是一臉懵逼。
學習協程的第一門課程,是要認識生成器
,有了生成器
的基礎,才能更好地理解協程
。
如果你是新手,那麼你應該知道迭代器
,對生成器
應該是比較陌生的吧。沒關係,看完這系列文章,你也能從小白成功過渡為Ptyhon高手。
再次提醒:
本系列所有的代碼均在Python3下編寫,也建議大家儘快投入到Python3的懷抱中來。
本文目錄
- 可迭代、迭代器、生成器
- 如何運行/激活生成器
- 生成器的執行狀態
- 生成器的異常處理
- 從生成器過渡到協程:yield
. 可迭代、迭代器、生成器
初學Python的時候,對於這三貨真的是傻傻分不清。甚至還認為他們是等價的。
其實,他們是不一樣的。
可迭代的對象,很好理解,我們很熟悉的:字元串
,list
,dict
,tuple
,deque
等
為了驗證我說的,需要藉助collections.abc
這個模塊(Python2沒有),使用isinstance()
來類別一個對象是否是可迭代的(Iterable
),是否是迭代器(Iterator
),是否是生成器(Generator
)。
輸出結果
從結果來看,這些可迭代對象都不是迭代器,也不是生成器。它們有一個共同點,就是它們都可以使用for
來循環。這一點,大家都知道,我們就不去驗證了。
擴展知識:
可迭代對象,是其內部實現了,
可以通過,__iter__
這個魔術方法。dir()
方法來查看是否有__iter__
來判斷一個變數是否是可迭代的。
接下來是,迭代器
。
迭代器
其實就只是多了一個函數而已。就是__next__()
,我們可以不再使用for
循環來間斷獲取元素值。而可以直接使用next()方法來實現。迭代器,是在可迭代的基礎上實現的。要創建一個迭代器,我們首先,得有一個可迭代對象。
現在就來看看,如何創建一個可迭代對象,並以可迭代對象為基礎創建一個迭代器。輸出
01234TrueFalseTrueTrue01234
如果上面的代碼太多,也可以看這邊,你更能理解。
擴展知識:
迭代器,是其內部實現了,__next__
這個魔術方法。(Python3.x)可以通過,dir()
方法來查看是否有__next__
來判斷一個變數是否是迭代器的。
接下來,是我們的重點,生成器
。
生成器的概念在 Python 2.2 中首次出現,之所以引入生成器,是為了實現一個在計算下一個值時不需要浪費空間的結構。
前面我們說,迭代器,是在可迭代的基礎上,加了一個next()方法。
而生成器,則是在迭代器的基礎上(可以用for循環,可以使用next()
),再實現了yield
。yield
是什麼東西呢,它相當於我們函數里的return。在每次next(),或者for遍歷的時候,都會yield這裡將新的值返回回去,並在這裡阻塞,等待下一次的調用。正是由於這個機制,才使用生成器在Python編程中大放異彩。實現節省內存,實現非同步編程。
如何創建一個生成器,主要有如下兩種方法
- 使用列表生成式
# 使用列表生成式,注意不是[],而是()L = (x * x for x in range(10))print(isinstance(L, Generator)) # True
- 實現yield的函數
# 實現了yield的函數def mygen(n): now = 0 while now < n: yield now now += 1if __name__ == __main__: gen = mygen(10) print(isinstance(gen, Generator)) # True
可迭代對象和迭代器,是將所有的值都生成存放在內存中,而生成器
則是需要元素才臨時生成,節省時間,節省空間。
. 如何運行/激活生成器
由於生成器並不是一次生成所有元素,而是一次一次的執行返回,那麼如何刺激生成器執行(或者說激活)呢?
激活主要有兩個方法
- 使用
next()
- 使用
generator.send(None)
分別看下例子,你就知道了。
輸出
0123
. 生成器的執行狀態
生成器在其生命周期中,會有如下四個狀態
GEN_CREATED
# 等待開始執行GEN_RUNNING
# 解釋器正在執行(只有在多線程應用中才能看到這個狀態)GEN_SUSPENDED
# 在yield表達式處暫停GEN_CLOSED
# 執行結束
通過代碼來感受一下,為了不增加代碼理解難度,GEN_RUNNING
這個狀態,我就不舉例了。有興趣的同學,可以去嘗試一下多線程。若有疑問,可在後台回復我。
輸出
GEN_CREATED0GEN_SUSPENDED1GEN_CLOSED
. 生成器的異常處理
在生成器工作過程中,若生成器不滿足生成元素的條件,就會
/應該
拋出異常(StopIteration
)。
通過列表生成式構建的生成器,其內部已經自動幫我們實現了拋出異常這一步。不信我們來看一下。
所以我們在自己定義一個生成器的時候,我們也應該在不滿足生成元素條件的時候,拋出異常。
拿上面的代碼來修改一下。. 從生成器過渡到協程:yield
通過上面的介紹,我們知道生成器為我們引入了暫停函數執行(yield
)的功能。當有了暫停的功能之後,人們就想能不能在生成器暫停的時候向其發送一點東西(其實上面也有提及:send(None)
)。這種向暫停的生成器發送信息的功能通過 PEP 342
進入 Python 2.5
中,並催生了 Python
中協程
的誕生。根據 wikipedia
中的定義
協程是為非搶佔式多任務產生子程序的計算機程序組件,協程允許不同入口點在不同位置暫停或開始執行程序。
注意從本質上而言,協程並不屬於語言中的概念,而是編程模型上的概念。
協程和線程,有相似點
,多個協程之間和線程一樣,只會交叉串列執行;也有不同點
,線程之間要頻繁進行切換,加鎖,解鎖,從複雜度和效率來看,和協程相比,這確是一個痛點。協程通過使用 yield
暫停生成器,可以將程序的執行流程交給其他的子程序,從而實現不同子程序的之間的交替執行。
下面通過一個簡明的演示來看看,如何向生成器中發送消息。
輸出。
0232
這裡解釋下為什麼這麼輸出。
重點是jump = yield index
這個語句。分成兩部分
yield index
是將indexreturn
給外部調用程序。jump = yield
可以接收外部程序通過send()發送的信息,並賦值給jump
以上這些,都是講協程並發的基礎必備知識,請一定要親自去實踐並理解它,不然後面的內容,將會變得枯燥無味,晦澀難懂。
下一章,我將講一個Python3.5新引入的語法:yield from
。篇幅也比較多,所以就單獨拿出來講。
好了,今天就講這些。
本系列文章首發於公眾號:PytherTime
未授權者禁止轉載。
點擊如下鏈接,關注公眾號獲取最新文章::
http://weixin.qq.com/r/Ly6wqCnEuHTHrW3L93s9
推薦閱讀:
※協程和多線程的小例子
※深入python協程的實現,帶你一層一層揭開協程的神秘面紗!
※開源一個超簡單的無棧協程原型(終)
※老協程,新協程?
※深入golang之---goroutine並發控制與通信