Python(3)---從迭代器到異步IO


目錄

1. 迭代(iteration)與迭代器(iterator)
  1.1 構建簡單迭代器
  1.2 調用next()
  1.3 迭代器狀態圖
2. 生成器(generator)
  2.1 創建簡單生成器
  2.2 利用函數定義生成器
3. 協程
  3.1 概念理解
  3.2 實例
4. 異步IO
  4.1 概念理解
  4.2 實例


1 迭代(iteration)與迭代器(iterator)

迭代是重復反饋過程的活動,其目的通常是為了接近并到達所需的目標或結果。每一次對過程的重復被稱為一次“迭代”,而每一次迭代得到的結果會被用來作為下一次迭代的初始值。(維基百科)

iterator是實現了iterator.__iter__()和iterator.__next__()方法的對象iterator.__iter__()方法返回的是iterator對象本身。

1.1 構建簡單迭代器

In [146]: test_iter = iter([i for i in range(1,4)]) 

In [147]: test_iter
Out[147]: <list_iterator at 0x84002a1f60>

返回列表迭代器對象,實際上實現了iterator.__iter__()。

1.2 調用next()

In [148]: next(test_iter)
Out[148]: 1

In [149]: next(test_iter)
Out[149]: 2

In [150]: next(test_iter)
Out[150]: 3

In [151]: next(test_iter)
Traceback (most recent call last):

  File "<ipython-input-151-ca50863582b2>", line 1, in <module>
    next(test_iter)

StopIteration
In [152]: 

可以看出next()實際調用了iterator.__next__()方法,每次調用更新iterator狀態,令其指向后一項,以便下一次調用并返回當前結果。

1.3 迭代器狀態圖

圖片來自網絡

  實際上迭代器就是實現迭代功能,先初始化迭代器,利用next()方法實現重復調用更新值,上次的終值時本次的初值。

2 生成器(generator)

通常帶有yield的函數便稱為生成器,yield是生成器執行的暫停恢復點,也是實現generator的__next__()方法的關鍵!可以對yield表達式進行賦值,也可以將yield表達式的值返回。簡而言之,generator是以更優雅的方式實現的iterator。

2.1 創建簡單生成器

其創建方法區別于列表創建方式,在此采用()而非[]

In [163]: test_gene = (x * x for x in range(1,4))

In [164]: test_gene
Out[164]: <generator object <genexpr> at 0x00000084002AD8E0>

In [166]: test_gene.__next__()
Out[166]: 1

In [167]: test_gene.__next__()
Out[167]: 4

In [168]: test_gene.__next__()
Out[168]: 9

In [169]: test_gene.__next__()
Traceback (most recent call last):

  File "<ipython-input-169-e6166353d257>", line 1, in <module>
    test_gene.__next__()

StopIteration

2.2 利用函數定義生成器

In [173]: def test_gene(a):
     ...:     print("第一步")
     ...:     yield a
     ...:     a += 1
     ...:     print("第二步")
     ...:     yield a
     ...:     a += 1
     ...:     print("第三步")
     ...:     yield a
     ...:     a += 1
     ...: 
     ...: 
     ...: g = test_gene(1)  

In [174]: g
Out[174]: <generator object test_gene at 0x0000008400295620>

In [175]: g.__next__()
第一步
Out[175]: 1

In [176]: g.__next__()
第二步
Out[176]: 2

In [177]: g.__next__()
第三步
Out[177]: 3

In [178]: g.__next__()
Traceback (most recent call last):

  File "<ipython-input-178-60e4a84be5d7>", line 1, in <module>
    g.__next__()

StopIteration

可以看出如果一個函數定義中包含yield關鍵字,那么這個函數就不再是一個普通函數,而是一個generator。在每次調用next()的時候執行:

  • 遇到yield語句返回;
  • 保留上下文環境(保留局部變量狀態);
  • 再次執行時從上次返回的yield語句處繼續執行。

總的來說生成器是一類特殊迭代器,一個產生值的函數 yield 是一種產生一個迭代器卻不需要構建迭代器的精密小巧的方法。很明顯可以看出生成器(Generator)是采用邊循環邊計算的機制,當我們只需訪問一個大列表的前幾個元素的情況下可以不必創建完整的list,從而節省大量的空間。

3 協程

3.1 概念理解

線程與進程,有自己的上下文,調度是由CPU來決定調度的;而協程也相對獨立,有自己的上下文,但是其切換由自己控制,由當前協程切換到其他協程由當前協程來控制(程序員控制),其實就是在一個線程中切換子線程。
  相比多線程有如下好處:一是協程極高的執行效率。因為子程序切換不是線程切換,而是由程序自身控制,因此,沒有線程切換的開銷,當線程數量越多,協程的性能優勢就越明顯。二是不需要多線程的鎖機制,因為只有一個線程,也不存在同時寫變量沖突,在協程中控制共享資源不加鎖,只需要判斷狀態就好了,所以執行效率比多線程高很多。
  協程、線程、進程在不同場景下的適用性不盡相同,在其他語言中,協程的其實是意義不大的多線程即可已解決I/O的問題,但是在python因為有GIL(Global Interpreter Lock 全局解釋器鎖 )在同一時間只有一個線程在工作,所以如果一個線程里面I/O操作特別多,協程就比較適用,如網絡請求。

3.2 實例

Python中的協程是通過“生成器(generator)”的概念實現的。這里引用廖雪峰Python教程中的例子,并將其修改為定外賣場景:

def shop():
    '''定義商家(生成器)
    '''        
    print("[-商家-] 開始接單 ......")
    print("###############################")
    r = "商家第1次接單完成"       # 初始化返回結果,并在啟動商家時,返回給消費者
    while True:    
        n = yield r  # (n = yield):商家通過yield接收消費者的消息,(yield r):返給結果  
        print("[-商家-] 正在處理第%s次訂單 ......" % n)
        print("[-商家-] 第%s次訂單正在配送中 ......" % n)
        print("[-商家-] 第%s次訂單已送達" % n)
        r = "商家第%s次接單完成" % (n+1)     # 商家信息,下個循環返回給消費者

def consumer(g):  
    '''定義消費者
    @g:商家生成器
    '''       
    print("[消費者] 開始下單 ......")
    r = g.send(None)    # 啟動商家生成器  
    n = 0
    while n < 5:
        n += 1
        print("[消費者] 已下第%s單" % n)
        print("[消費者] 接受商家消息:%s" % r)
        r = g.send(n)   # 向商家發送下單消息并準備接收結果。此時會切換到消費者執行
        print("###############################")
    g.close()           # 關閉商家生成器
    print("[消費者] 停止接單 ......")

if __name__ == "__main__":
    g = shop() 
    consumer(g)
[消費者] 開始下單 ......
[-商家-] 開始接單 ......
###############################
[消費者] 已下第1單
[消費者] 接受商家消息:商家第1次接單完成
[-商家-] 正在處理第1次訂單 ......
[-商家-] 第1次訂單正在配送中 ......
[-商家-] 第1次訂單已送達
###############################
[消費者] 已下第2單
[消費者] 接受商家消息:商家第2次接單完成
[-商家-] 正在處理第2次訂單 ......
[-商家-] 第2次訂單正在配送中 ......
[-商家-] 第2次訂單已送達
###############################
[消費者] 已下第3單
[消費者] 接受商家消息:商家第3次接單完成
[-商家-] 正在處理第3次訂單 ......
[-商家-] 第3次訂單正在配送中 ......
[-商家-] 第3次訂單已送達
###############################
[消費者] 已下第4單
[消費者] 接受商家消息:商家第4次接單完成
[-商家-] 正在處理第4次訂單 ......
[-商家-] 第4次訂單正在配送中 ......
[-商家-] 第4次訂單已送達
###############################
[消費者] 已下第5單
[消費者] 接受商家消息:商家第5次接單完成
[-商家-] 正在處理第5次訂單 ......
[-商家-] 第5次訂單正在配送中 ......
[-商家-] 第5次訂單已送達
###############################
[消費者] 停止接單 ......

4 異步IO實例

4.1 概念理解

異步是區別于同步,這里的同步指的并不是所有線程同時進行,而是所有線程在時間軸上有序進行。在實際的IO操作的過程中,當前線程被掛起,而其他需要CPU執行的代碼就無法被當前線程執行了。異步正是為解決CPU高速執行能力和IO設備的龜速嚴重不匹配,當代碼需要執行一個耗時的IO操作時,它只發出IO指令,并不等待IO結果,然后就去執行其他代碼了。一段時間后,當IO返回結果時,再通知CPU進行處理。
  異步IO是基于CPU與IO處理速度不一致并為了充分利用資源的方法之一,在上一篇《Python知識(1)——并發編程》中記錄到的多線程與多進程也是該問題的處理方法之一。

圖片來自網絡

4.2 實例

只有協程還不夠,還不足以實現異步IO,我們必須實現消息循環和狀態的控制,在此我們先了解一下幾個關鍵詞。

  • asyncio
    Python 3.4版本引入的標準庫,直接內置了對異步IO的支持。asyncio的編程模型就是一個消息循環。我們從asyncio模塊中直接獲取一個EventLoop的引用,然后把需要執行的協程扔到EventLoop中執行,就實現了異步IO。

  • async/await
    python3.5中新加入的特性, 將異步從原來的yield 寫法中解放出來,變得更加直觀。其中async修飾的函數為異步函數,await 替換了yield from, 表示這一步為異步操作。

  • aiohttp
    一個提供異步web服務的庫,分為服務器端和客戶端。這里主要使用其客戶端。

import asyncio
import aiohttp
async def get(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            print(url, resp.status)
            print(url, await resp.text())

loop = asyncio.get_event_loop()     # 得到一個事件循環模型
urls = ["https://movie.douban.com/tag/科幻?start="+str(1)+"&type=T" for i in range(1,4)]
tasks = [ get(url) for url in urls] # 初始化任務列表

loop.run_until_complete(asyncio.wait(tasks))    # 執行任務
loop.close()                        # 關閉事件循環列表

參考與拓展閱讀:
[1]Python生成器詳解 | 投稿
[2]廖雪峰Python教程
[3]Python學習:異步IO:協程和asyncio
[4]Python【第十篇】協程、異步IO
[5]Python進階:理解Python中的異步IO和協程(Coroutine),并應用在爬蟲中
[6]異步爬蟲: async/await 與 aiohttp的使用,以及例子
[7]Python 異步網絡爬蟲(1)


個人Github
個人博客DebugNLP
歡迎各路同學互相交流

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容