理解Python協程:從yield/send到yield from再到async/await

Python中的協程大概經歷了以下三個階段: 
1. 最初的生成器變形yield/send 
2. 引入@asyncio.coroutine和yield from 
3. 在最近的Python3.5版本中引入async/await關鍵字app

 

1、生成器變形yield/send

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進行迭代。生成器函數最大的特色是能夠接受外部傳入的一個變量,並根據變量內容計算結果後返回。 
這一切都是靠生成器內部的send()函數實現的。dom

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'))

 

上面生成器函數中最關鍵也是最易理解錯的,就是receive=yield value這句,若是對循環體的執行步驟理解錯誤,就會失之毫釐,差之千里。 異步

其實receive=yield value包含了3個步驟: async

一、向函數外拋出(返回)value 函數

二、暫停(pause),等待next()或send()恢復 oop

三、賦值receive=MockGetValue() 。 這個MockGetValue()是假想函數,用來接收send()發送進來的值spa

 

執行流程: .net

一、經過g.send(None)或者next(g)啓動生成器函數,並執行到第一個yield語句結束的位置。這裏是關鍵,不少人就是在這裏搞糊塗的。運行receive=yield value語句時,咱們按照開始說的拆開來看,實際程序只執行了1,2兩步,程序返回了value值,並暫停(pause),並無執行第3步給receive賦值。所以yield value會輸出初始值0。這裏要特別注意:在啓動生成器函數時只能send(None),若是試圖輸入其它的值都會獲得錯誤提示信息。線程

 

二、經過g.send('hello'),會傳入hello,從上次暫停的位置繼續執行,那麼就是運行第3步,賦值給receive。而後計算出value的值,並回到while頭部,遇到yield value,程序再次執行了1,2兩步,程序返回了value值,並暫停(pause)。此時yield value會輸出」got: hello」,並等待send()激活。設計

 

三、經過g.send(123456),會重複第2步,最後輸出結果爲」got: 123456″。

 

四、當咱們g.send(‘e’)時,程序會執行break而後推出循環,最後整個函數執行完畢,因此會獲得StopIteration異常。

 

從上面能夠看出, 在第一次send(None)啓動生成器(執行1–>2,一般第一次返回的值沒有什麼用)以後,對於外部的每一次send(),生成器的實際在循環中的運行順序是3–>1–>2,也就是先獲取值,而後dosomething,而後返回一個值,再暫停等待。

 

2、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) 




4

這說明yield就是將range這個可迭代對象直接返回了。 

而yield from解析了range對象,將其中每個item返回了。 

yield from iterable本質上等於for item in iterable: yield item的縮寫版 

來看一下例子,假設咱們已經編寫好一個斐波那契數列函數

 

 

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不是一個普通函數,而是一個生成器。所以fab(5)並無執行函數,而是返回一個生成器對象(生成器必定是迭代器iterator,迭代器必定是可迭代對象iterable) 
如今咱們來看一下,假設要在fab()的基礎上實現一個函數,調用起始都要記錄日誌

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=' ')

如今使用yield from代替for循環

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=' ')

再強調一遍:yield from後面必須跟iterable對象(能夠是生成器,迭代器)

yield from在asyncio模塊中得以發揚光大。以前都是咱們手工切換協程,如今當聲明函數爲協程後,咱們經過事件循環來調度協程。

先看示例代碼:

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語法可讓咱們方便地調用另外一個generator。 

本例中yield from後面接的asyncio.sleep()是一個coroutine(裏面也用了yield from),因此線程不會等待asyncio.sleep(),而是直接中斷並執行下一個消息循環。當asyncio.sleep()返回時,線程就能夠從yield from拿到返回值(此處是None),而後接着執行下一行語句。 

asyncio是一個基於事件循環的實現異步I/O的模塊。經過yield from,咱們能夠將協程asyncio.sleep的控制權交給事件循環,而後掛起當前協程;以後,由事件循環決定什麼時候喚醒asyncio.sleep,接着向後執行代碼。 

協程之間的調度都是由事件循環決定。 

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

4、async和await

弄清楚了asyncio.coroutine和yield from以後,在Python3.5中引入的async和await就不難理解了:能夠將他們理解成asyncio.coroutine/yield from的完美替身。固然,從Python設計的角度來講,async/await讓協程表面上獨立於生成器而存在,將細節都隱藏於asyncio模塊之下,語法更清晰明瞭。 

加入新的關鍵字 async ,能夠將任何一個普通函數變成協程

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,該函數就變成一個協程了。

可是async對生成器是無效的。async沒法將一個生成器轉換成協程。 
仍是剛纔那段代碼,咱們把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>

 

並非coroutine 協程對象

因此咱們的協程代碼應該是這樣的

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)

要運行協程,要用事件循環 
在上面的代碼下面加上:

if __name__ == '__main__':
        loop = asyncio.get_event_loop()
        tasks = [
        c1,
        c2
        ]
        loop.run_until_complete(asyncio.wait(tasks))
        print('All fib finished.')
        loop.close()

 

 

原文出處:

--------------------- 

做者:唐大麥 

來源:CSDN 

原文:https://blog.csdn.net/soonfly/article/details/78361819 

版權聲明:本文爲博主原創文章,轉載請附上博文連接!

 

 

 

 

Python中的協程大概經歷了以下三個階段: 1. 最初的生成器變形yield/send 2. 引入@asyncio.coroutine和yield from 3. 在最近的Python3.5版本中引入async/await關鍵字1、生成器變形yield/send普通函數中若是出現了yield關鍵字,那麼該函數就再也不是普通函數,而是一個生成器。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>123456789像上面代碼中的c就是一個生成器。生成器就是一種迭代器,可使用for進行迭代。生成器函數最大的特色是能夠接受外部傳入的一個變量,並根據變量內容計算結果後返回。 這一切都是靠生成器內部的send()函數實現的。def gen():    value=0    while True:        receive=yield value        if receive=='e':            break        value = 'got: %s' % receiveg=gen()print(g.send(None))    print(g.send('hello'))print(g.send(123456))print(g.send('e'))12345678910111213上面生成器函數中最關鍵也是最易理解錯的,就是receive=yield value這句,若是對循環體的執行步驟理解錯誤,就會失之毫釐,差之千里。 其實receive=yield value包含了3個步驟: 一、向函數外拋出(返回)value 二、暫停(pause),等待next()或send()恢復 三、賦值receive=MockGetValue() 。 這個MockGetValue()是假想函數,用來接收send()發送進來的值執行流程: 一、經過g.send(None)或者next(g)啓動生成器函數,並執行到第一個yield語句結束的位置。這裏是關鍵,不少人就是在這裏搞糊塗的。運行receive=yield value語句時,咱們按照開始說的拆開來看,實際程序只執行了1,2兩步,程序返回了value值,並暫停(pause),並無執行第3步給receive賦值。所以yield value會輸出初始值0。這裏要特別注意:在啓動生成器函數時只能send(None),若是試圖輸入其它的值都會獲得錯誤提示信息。二、經過g.send('hello'),會傳入hello,從上次暫停的位置繼續執行,那麼就是運行第3步,賦值給receive。而後計算出value的值,並回到while頭部,遇到yield value,程序再次執行了1,2兩步,程序返回了value值,並暫停(pause)。此時yield value會輸出」got: hello」,並等待send()激活。三、經過g.send(123456),會重複第2步,最後輸出結果爲」got: 123456″。四、當咱們g.send(‘e’)時,程序會執行break而後推出循環,最後整個函數執行完畢,因此會獲得StopIteration異常。從上面能夠看出, 在第一次send(None)啓動生成器(執行1–>2,一般第一次返回的值沒有什麼用)以後,對於外部的每一次send(),生成器的實際在循環中的運行順序是3–>1–>2,也就是先獲取值,而後dosomething,而後返回一個值,再暫停等待。2、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)123456789101112輸出: range(0, 5) 0 1 2 3 4這說明yield就是將range這個可迭代對象直接返回了。 而yield from解析了range對象,將其中每個item返回了。 yield from iterable本質上等於for item in iterable: yield item的縮寫版 來看一下例子,假設咱們已經編寫好一個斐波那契數列函數def fab(max):     n,a,b = 0,0,1     while n < max:          yield b          # print b          a, b = b, a + b          n = n + 1f=fab(5) 12345678fab不是一個普通函數,而是一個生成器。所以fab(5)並無執行函數,而是返回一個生成器對象(生成器必定是迭代器iterator,迭代器必定是可迭代對象iterable) 如今咱們來看一下,假設要在fab()的基礎上實現一個函數,調用起始都要記錄日誌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=' ')12345678如今使用yield from代替for循環import loggingdef f_wrapper2(fun_iterable):    print('start')    yield from fun_iterable  #注意此處必須是一個可生成對象    print('end')wrap = f_wrapper2(fab(5))for i in wrap:    print(i,end=' ')12345678再強調一遍:yield from後面必須跟iterable對象(能夠是生成器,迭代器)3、asyncio.coroutine和yield fromyield from在asyncio模塊中得以發揚光大。以前都是咱們手工切換協程,如今當聲明函數爲協程後,咱們經過事件循環來調度協程。先看示例代碼:import asyncio,random@asyncio.coroutinedef 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.coroutinedef 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 += 1if __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()12345678910111213141516171819202122232425262728293031323334yield from語法可讓咱們方便地調用另外一個generator。 本例中yield from後面接的asyncio.sleep()是一個coroutine(裏面也用了yield from),因此線程不會等待asyncio.sleep(),而是直接中斷並執行下一個消息循環。當asyncio.sleep()返回時,線程就能夠從yield from拿到返回值(此處是None),而後接着執行下一行語句。 asyncio是一個基於事件循環的實現異步I/O的模塊。經過yield from,咱們能夠將協程asyncio.sleep的控制權交給事件循環,而後掛起當前協程;以後,由事件循環決定什麼時候喚醒asyncio.sleep,接着向後執行代碼。 協程之間的調度都是由事件循環決定。 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 iterable4、async和await弄清楚了asyncio.coroutine和yield from以後,在Python3.5中引入的async和await就不難理解了:能夠將他們理解成asyncio.coroutine/yield from的完美替身。固然,從Python設計的角度來講,async/await讓協程表面上獨立於生成器而存在,將細節都隱藏於asyncio模塊之下,語法更清晰明瞭。 加入新的關鍵字 async ,能夠將任何一個普通函數變成協程import time,asyncio,randomasync 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>12345678910在上面程序中,咱們在前面加上async,該函數就變成一個協程了。可是async對生成器是無效的。async沒法將一個生成器轉換成協程。 仍是剛纔那段代碼,咱們把print改爲yieldasync 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)12345678能夠看到輸出<async_generator object mygen at 0x02AA7170>1並非coroutine 協程對象因此咱們的協程代碼應該是這樣的import time,asyncio,randomasync 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)1234567891011要運行協程,要用事件循環 在上面的代碼下面加上:if __name__ == '__main__':        loop = asyncio.get_event_loop()        tasks = [        c1,        c2        ]        loop.run_until_complete(asyncio.wait(tasks))        print('All fib finished.')        loop.close()123456789就能夠看到交替執行的效果。--------------------- 做者:唐大麥 來源:CSDN 原文:https://blog.csdn.net/soonfly/article/details/78361819 版權聲明:本文爲博主原創文章,轉載請附上博文連接!

相關文章
相關標籤/搜索