目錄
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)