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


目錄

1. 迭代(iteration)與迭代器(iterator)
  1.1 構(gòu)建簡單迭代器
  1.2 調(diào)用next()
  1.3 迭代器狀態(tài)圖
2. 生成器(generator)
  2.1 創(chuàng)建簡單生成器
  2.2 利用函數(shù)定義生成器
3. 協(xié)程
  3.1 概念理解
  3.2 實例
4. 異步IO
  4.1 概念理解
  4.2 實例


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

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

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

1.1 構(gòu)建簡單迭代器

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

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

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

1.2 調(diào)用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()實際調(diào)用了iterator.__next__()方法叶圃,每次調(diào)用更新iterator狀態(tài)宴抚,令其指向后一項勒魔,以便下一次調(diào)用并返回當前結(jié)果甫煞。

1.3 迭代器狀態(tài)圖

圖片來自網(wǎng)絡

  實際上迭代器就是實現(xiàn)迭代功能,先初始化迭代器冠绢,利用next()方法實現(xiàn)重復調(diào)用更新值抚吠,上次的終值時本次的初值。

2 生成器(generator)

通常帶有yield的函數(shù)便稱為生成器弟胀,yield是生成器執(zhí)行的暫涂Γ恢復點,也是實現(xiàn)generator的__next__()方法的關鍵孵户!可以對yield表達式進行賦值萧朝,也可以將yield表達式的值返回。簡而言之夏哭,generator是以更優(yōu)雅的方式實現(xiàn)的iterator剪勿。

2.1 創(chuàng)建簡單生成器

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

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 利用函數(shù)定義生成器

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

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

  • 遇到y(tǒng)ield語句返回械念;
  • 保留上下文環(huán)境(保留局部變量狀態(tài));
  • 再次執(zhí)行時從上次返回的yield語句處繼續(xù)執(zhí)行运悲。

總的來說生成器是一類特殊迭代器龄减,一個產(chǎn)生值的函數(shù) yield 是一種產(chǎn)生一個迭代器卻不需要構(gòu)建迭代器的精密小巧的方法。很明顯可以看出生成器(Generator)是采用邊循環(huán)邊計算的機制班眯,當我們只需訪問一個大列表的前幾個元素的情況下可以不必創(chuàng)建完整的list希停,從而節(jié)省大量的空間。

3 協(xié)程

3.1 概念理解

線程與進程署隘,有自己的上下文宠能,調(diào)度是由CPU來決定調(diào)度的;而協(xié)程也相對獨立磁餐,有自己的上下文违崇,但是其切換由自己控制,由當前協(xié)程切換到其他協(xié)程由當前協(xié)程來控制(程序員控制)诊霹,其實就是在一個線程中切換子線程羞延。
  相比多線程有如下好處:一是協(xié)程極高的執(zhí)行效率。因為子程序切換不是線程切換脾还,而是由程序自身控制伴箩,因此,沒有線程切換的開銷鄙漏,當線程數(shù)量越多嗤谚,協(xié)程的性能優(yōu)勢就越明顯棺蛛。二是不需要多線程的鎖機制,因為只有一個線程呵恢,也不存在同時寫變量沖突鞠值,在協(xié)程中控制共享資源不加鎖,只需要判斷狀態(tài)就好了渗钉,所以執(zhí)行效率比多線程高很多彤恶。
  協(xié)程、線程鳄橘、進程在不同場景下的適用性不盡相同声离,在其他語言中,協(xié)程的其實是意義不大的多線程即可已解決I/O的問題瘫怜,但是在python因為有GIL(Global Interpreter Lock 全局解釋器鎖 )在同一時間只有一個線程在工作术徊,所以如果一個線程里面I/O操作特別多,協(xié)程就比較適用鲸湃,如網(wǎng)絡請求赠涮。

3.2 實例

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

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

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)   # 向商家發(fā)送下單消息并準備接收結(jié)果。此時會切換到消費者執(zhí)行
        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 概念理解

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

圖片來自網(wǎng)絡

4.2 實例

只有協(xié)程還不夠损合,還不足以實現(xiàn)異步IO省艳,我們必須實現(xiàn)消息循環(huán)和狀態(tài)的控制,在此我們先了解一下幾個關鍵詞嫁审。

  • asyncio
    Python 3.4版本引入的標準庫跋炕,直接內(nèi)置了對異步IO的支持。asyncio的編程模型就是一個消息循環(huán)律适。我們從asyncio模塊中直接獲取一個EventLoop的引用辐烂,然后把需要執(zhí)行的協(xié)程扔到EventLoop中執(zhí)行,就實現(xiàn)了異步IO捂贿。

  • async/await
    python3.5中新加入的特性纠修, 將異步從原來的yield 寫法中解放出來,變得更加直觀厂僧。其中async修飾的函數(shù)為異步函數(shù)扣草,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()     # 得到一個事件循環(huán)模型
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))    # 執(zhí)行任務
loop.close()                        # 關閉事件循環(huán)列表

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


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

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蕴坪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子敬锐,更是在濱河造成了極大的恐慌背传,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件台夺,死亡現(xiàn)場離奇詭異径玖,居然都是意外死亡,警方通過查閱死者的電腦和手機颤介,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門梳星,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人滚朵,你說我怎么就攤上這事冤灾。” “怎么了辕近?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵韵吨,是天一觀的道長。 經(jīng)常有香客問我移宅,道長归粉,這世上最難降的妖魔是什么椿疗? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮糠悼,結(jié)果婚禮上届榄,老公的妹妹穿的比我還像新娘。我一直安慰自己倔喂,他們只是感情好铝条,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著滴劲,像睡著了一般攻晒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上班挖,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天鲁捏,我揣著相機與錄音,去河邊找鬼萧芙。 笑死给梅,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的双揪。 我是一名探鬼主播动羽,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼渔期!你這毒婦竟也來了运吓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤疯趟,失蹤者是張志新(化名)和其女友劉穎拘哨,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體信峻,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡倦青,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了盹舞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片产镐。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖踢步,靈堂內(nèi)的尸體忽然破棺而出癣亚,到底是詐尸還是另有隱情,我是刑警寧澤获印,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布逃糟,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏绰咽。R本人自食惡果不足惜菇肃,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望取募。 院中可真熱鬧琐谤,春花似錦、人聲如沸玩敏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽旺聚。三九已至织阳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間砰粹,已是汗流浹背唧躲。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留碱璃,地道東北人弄痹。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像嵌器,于是被迫代替她去往敵國和親肛真。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容