1. 程式人生 > >python3從生成器到協程

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不但可以返回一個值,它還可以接收呼叫者發出的引數。

未完待續,學習自:廖雪峰的官方教程
指令碼之家