Python中的協(xié)程大概經(jīng)歷了如下三個階段:
- 最初的生成器變形yield/send
- 引入@asyncio.coroutine和yield from
- 在最近的Python3.5版本中引入async/await關(guān)鍵字
一垦藏、生成器變形yield/send
普通函數(shù)中如果出現(xiàn)了yield關(guān)鍵字廷臼,那么該函數(shù)就不再是普通函數(shù)搔确,而是一個生成器陪竿。
def mygen(alist):
while len(alist) > 0:
c = randint(0, len(alist)-1)
yield alist.pop(c)
a = ["aa","bb","cc"]
c=mygen(a)
print(c)
// 輸出:<generator object mygen at 0x02E5BF00>
像上面代碼中的c就是一個生成器。生成器就是一種迭代器简卧,可以使用for進(jìn)行迭代兔魂。生成器函數(shù)最大的特點是可以接受外部傳入的一個變量,并根據(jù)變量內(nèi)容計算結(jié)果后返回举娩。
這一切都是靠生成器內(nèi)部的send()函數(shù)實現(xiàn)的析校。
def gen():
value=0
while True:
receive=yield value
if receive=='e':
break
value = 'got: %s' % receive
g=gen()
print(g.send(None))
print(g.send('hello'))
print(g.send(123456))
print(g.send('e'))
上面生成器函數(shù)中最關(guān)鍵也是最易理解錯的,就是receive=yield value這句,如果對循環(huán)體的執(zhí)行步驟理解錯誤铜涉,就會失之毫厘智玻,差之千里。
其實receive=yield value包含了3個步驟:
1芙代、向函數(shù)外拋出(返回)value
2尚困、暫停(pause),等待next()或send()恢復(fù)
3链蕊、賦值receive=MockGetValue() 事甜。 這個MockGetValue()是假想函數(shù),用來接收send()發(fā)送進(jìn)來的值
執(zhí)行流程:
1滔韵、通過g.send(None)或者next(g)啟動生成器函數(shù)逻谦,并執(zhí)行到第一個yield語句結(jié)束的位置。這里是關(guān)鍵陪蜻,很多人就是在這里搞糊涂的邦马。運行receive=yield value語句時,我們按照開始說的拆開來看宴卖,實際程序只執(zhí)行了1滋将,2兩步,程序返回了value值症昏,并暫停(pause)随闽,并沒有執(zhí)行第3步給receive賦值。因此yield value會輸出初始值0肝谭。這里要特別注意:在啟動生成器函數(shù)時只能send(None),如果試圖輸入其它的值都會得到錯誤提示信息掘宪。
2、通過g.send('hello')攘烛,會傳入hello魏滚,從上次暫停的位置繼續(xù)執(zhí)行,那么就是運行第3步坟漱,賦值給receive鼠次。然后計算出value的值,并回到while頭部,遇到y(tǒng)ield value腥寇,程序再次執(zhí)行了1成翩,2兩步,程序返回了value值花颗,并暫停(pause)捕传。此時yield value會輸出”got: hello”惠拭,并等待send()激活扩劝。
3、通過g.send(123456)职辅,會重復(fù)第2步棒呛,最后輸出結(jié)果為”got: 123456″。
4域携、當(dāng)我們g.send(‘e’)時簇秒,程序會執(zhí)行break然后推出循環(huán),最后整個函數(shù)執(zhí)行完畢秀鞭,所以會得到StopIteration異常趋观。
從上面可以看出, 在第一次send(None)啟動生成器(執(zhí)行1–>2锋边,通常第一次返回的值沒有什么用)之后皱坛,對于外部的每一次send(),生成器的實際在循環(huán)中的運行順序是3–>1–>2豆巨,也就是先獲取值剩辟,然后dosomething,然后返回一個值往扔,再暫停等待贩猎。
二、yield from
看一段代碼:
def g1():
yield range(5)
def g2():
yield from range(5)
it1 = g1()
it2 = g2()
for x in it1:
print(x)
for x in it2:
print(x)
輸出:
range(0, 5)
0
1
2
3
4
這說明yield就是將range這個可迭代對象直接返回了萍膛。
而yield from解析了range對象吭服,將其中每一個item返回了。
yield from iterable本質(zhì)上等于for item in iterable: yield item的縮寫版
來看一下例子蝗罗,假設(shè)我們已經(jīng)編寫好一個斐波那契數(shù)列函數(shù)
def fab(max):
n,a,b = 0,0,1
while n < max:
yield b
# print b
a, b = b, a + b
n = n + 1
f=fab(5)
fab不是一個普通函數(shù)噪馏,而是一個生成器。因此fab(5)并沒有執(zhí)行函數(shù)绿饵,而是返回一個生成器對象(生成器一定是迭代器iterator欠肾,迭代器一定是可迭代對象iterable)
現(xiàn)在我們來看一下,假設(shè)要在fab()的基礎(chǔ)上實現(xiàn)一個函數(shù)拟赊,調(diào)用起始都要記錄日志
def f_wrapper(fun_iterable):
print('start')
for item in fun_iterable:
yield item
print('end')
wrap = f_wrapper(fab(5))
for i in wrap:
print(i,end=' ')
現(xiàn)在使用yield from代替for循環(huán)
import logging
def f_wrapper2(fun_iterable):
print('start')
yield from fun_iterable #注意此處必須是一個可生成對象
print('end')
wrap = f_wrapper2(fab(5))
for i in wrap:
print(i,end=' ')
再強(qiáng)調(diào)一遍:yield from后面必須跟iterable對象(可以是生成器刺桃,迭代器)
三、asyncio.coroutine和yield from
yield from在asyncio
模塊中得以發(fā)揚光大吸祟。之前都是我們手工切換協(xié)程瑟慈,現(xiàn)在當(dāng)聲明函數(shù)為協(xié)程后桃移,我們通過事件循環(huán)來調(diào)度協(xié)程。
先看示例代碼:
import asyncio,random
@asyncio.coroutine
def smart_fib(n):
index = 0
a = 0
b = 1
while index < n:
sleep_secs = random.uniform(0, 0.2)
yield from asyncio.sleep(sleep_secs) #通常yield from后都是接的耗時操作
print('Smart one think {} secs to get {}'.format(sleep_secs, b))
a, b = b, a + b
index += 1
@asyncio.coroutine
def stupid_fib(n):
index = 0
a = 0
b = 1
while index < n:
sleep_secs = random.uniform(0, 0.4)
yield from asyncio.sleep(sleep_secs) #通常yield from后都是接的耗時操作
print('Stupid one think {} secs to get {}'.format(sleep_secs, b))
a, b = b, a + b
index += 1
if __name__ == '__main__':
loop = asyncio.get_event_loop()
tasks = [
smart_fib(10),
stupid_fib(10),
]
loop.run_until_complete(asyncio.wait(tasks))
print('All fib finished.')
loop.close()
yield from語法可以讓我們方便地調(diào)用另一個generator葛碧。
本例中yield from后面接的asyncio.sleep()是一個coroutine(里面也用了yield from)借杰,所以線程不會等待asyncio.sleep(),而是直接中斷并執(zhí)行下一個消息循環(huán)进泼。當(dāng)asyncio.sleep()返回時蔗衡,線程就可以從yield from拿到返回值(此處是None),然后接著執(zhí)行下一行語句乳绕。
asyncio是一個基于事件循環(huán)的實現(xiàn)異步I/O的模塊绞惦。通過yield from,我們可以將協(xié)程asyncio.sleep的控制權(quán)交給事件循環(huán)洋措,然后掛起當(dāng)前協(xié)程济蝉;之后,由事件循環(huán)決定何時喚醒a(bǔ)syncio.sleep,接著向后執(zhí)行代碼菠发。
協(xié)程之間的調(diào)度都是由事件循環(huán)決定王滤。
yield from asyncio.sleep(sleep_secs) 這里不能用time.sleep(1)因為time.sleep()返回的是None,它不是iterable滓鸠,還記得前面說的yield from后面必須跟iterable對象(可以是生成器雁乡,迭代器)。
所以會報錯:
yield from time.sleep(sleep_secs)
TypeError: ‘NoneType’ object is not iterable
四哥力、async和await
弄清楚了asyncio.coroutine和yield from之后蔗怠,在Python3.5中引入的async和await就不難理解了:可以將他們理解成asyncio.coroutine/yield from的完美替身。當(dāng)然吩跋,從Python設(shè)計的角度來說寞射,async/await讓協(xié)程表面上獨立于生成器而存在,將細(xì)節(jié)都隱藏于asyncio模塊之下锌钮,語法更清晰明了桥温。
加入新的關(guān)鍵字 async ,可以將任何一個普通函數(shù)變成協(xié)程
import time,asyncio,random
async def mygen(alist):
while len(alist) > 0:
c = randint(0, len(alist)-1)
print(alist.pop(c))
a = ["aa","bb","cc"]
c=mygen(a)
print(c)
輸出:
<coroutine object mygen at 0x02C6BED0>
在上面程序中梁丘,我們在前面加上async侵浸,該函數(shù)就變成一個協(xié)程了。
但是async對生成器是無效的氛谜。async無法將一個生成器轉(zhuǎn)換成協(xié)程掏觉。
還是剛才那段代碼,我們把print改成yield
async def mygen(alist):
while len(alist) > 0:
c = randint(0, len(alist)-1)
yield alist.pop(c)
a = ["ss","dd","gg"]
c=mygen(a)
print(c)
可以看到輸出
<async_generator object mygen at 0x02AA7170>
1
并不是coroutine 協(xié)程對象
所以我們的協(xié)程代碼應(yīng)該是這樣的
import time,asyncio,random
async def mygen(alist):
while len(alist) > 0:
c = random.randint(0, len(alist)-1)
print(alist.pop(c))
await asyncio.sleep(1)
strlist = ["ss","dd","gg"]
intlist=[1,2,5,6]
c1=mygen(strlist)
c2=mygen(intlist)
print(c1)
要運行協(xié)程值漫,要用事件循環(huán)
在上面的代碼下面加上:
if __name__ == '__main__':
loop = asyncio.get_event_loop()
tasks = [
c1,
c2
]
loop.run_until_complete(asyncio.wait(tasks))
print('All fib finished.')
loop.close()
就可以看到交替執(zhí)行的效果澳腹。
本文參考
http://python.jobbole.com/81911/
http://python.jobbole.com/86069/
原文:https://blog.csdn.net/soonfly/article/details/78361819