python3從生成器到協程
生成器
通過列表生成式,我們可以直接建立一個列表。但是,受到記憶體限制,列表容量肯定是有限的。而且,建立一個包含100萬個元素的列表,不僅佔用很大的儲存空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。
所以,如果列表元素可以按照某種演算法推算出來,那我們是否可以在迴圈的過程中不斷推算出後續的元素呢?這樣就不必建立完整的list,從而節省大量的空間。在Python中,這種一邊迴圈一邊計算的機制,稱為生成器:generator。
要建立一個generator,有很多種方法。第一種方法很簡單,只要把一個列表生成式的[]改成(),就建立了一個generator:
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
高階一點的生成器就是一個包含yield
關鍵字的函式,不過執行過程有點詭異:
先來看一個例子
def fun(): print('start') for i in range(3): a = yield i print("a:",a) print('end') f = fun() f.send(None) print('NNN') f.send(100) print('111') f.send(200) print('222\n\n') # yield語句必須在迴圈裡才有意義,啟動生成器必須用send(None)(等價於__next()__方法) # send(None)之後,函式執行到`yield i(0)`並執行完,並不給a賦值 # send(100)之後,先給a賦值為a = 100,print(a)輸出 100,然後下一次迴圈執行完`yield i(1)` # send(200)之後,先給a賦值為a = 200,print(a)輸出 200,然後下一次迴圈執行完`yield i(2)`
其輸出結果如下,上面的註釋是我根據結果推匯出來的
start
NNN
a: 100
111
a: 200
222
再來一個例子:
def fun(): print('start') for i in range(3): a = yield i print("a:",a) print('end') f = fun() f.__next__() print('NNN') f.__next__() print('111') f.__next__() print('222\n\n') # yield語句必須在迴圈裡才有意義,啟動生成器必須用send(None)(等價於__next__()方法)
其輸出結果如下:
start
NNN
a: None
111
a: None
222
開始我還懷疑__next__()方法只是第一次不賦值,現在看來是每次都不賦值啊,其實for迴圈也可以改成while迴圈。
那__next__()函式的真正意義何在,我們注意到,他是有返回值的
def fun():
print('start')
for i in range(3):
a = yield i
print("a:",a)
print('end')
f = fun()
print("next",f.__next__())
print('NNN')
print("next",f.__next__())
print('111')
print("next",f.__next__())
print('222\n\n')
# yield語句必須在迴圈裡才有意義,啟動生成器必須用send(None)(等價於__next__()方法)
輸出結果:
start
next 0
NNN
a: None
next 1
111
a: None
next 2
222
所以__next__()真正作用是返回yield表示式等號右邊變數值,同時驗證了只有send(not none)才能給a賦值,不然a一直未定義。其實,send()函式的返回值也是yield表示式等號右邊變數值。
協程
瞭解了生成器之後,我們就可以學習協程了,協程的定義如下:
協程,又稱微執行緒,纖程。英文名Coroutine。
協程執行有點像多執行緒,但協程的特點在於是一個執行緒執行,那和多執行緒比,協程有何優勢?
最大的優勢就是協程極高的執行效率。因為子程式切換不是執行緒切換,而是由程式自身控制,因此,沒有執行緒切換的開銷,和多執行緒比,執行緒數量越多,協程的效能優勢就越明顯。
第二大優勢就是不需要多執行緒的鎖機制,因為只有一個執行緒,也不存在同時寫變數衝突,在協程中控制共享資源不加鎖,只需要判斷狀態就好了,所以執行效率比多執行緒高很多。
因為協程是一個執行緒執行,那怎麼利用多核CPU呢?最簡單的方法是多程序+協程,既充分利用多核,又充分發揮協程的高效率,可獲得極高的效能。
Python對協程的支援是通過生成器實現的。
在生成器中,我們不但可以通過for迴圈來迭代,還可以不斷呼叫__next__()函式獲取由yield語句返回的下一個值。
但是Python的yield不但可以返回一個值,它還可以接收呼叫者發出的引數。