生成器 yield和協程

yield和協程
推薦博客:https://blog.csdn.net/soonfly/article/details/78361819
yield具備return的功能,只是yield是中斷函數(更準確的說是生成器),等待下一個next()或send()再繼續執行至下一個yield
協程就是利用一個cpu運行一個線程,經過yield分時段執行多個任務,即當執行的任務遇到IO阻塞等待時,cpu就不執行這個任務轉而順序執行下一個任務
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包含了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
二、經過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異常。

示例2
def generator():
    print(123)
    content = yield 1
    print('=======',content)
    print(456)
    yield2

g = generator()
ret = g.__next__()
print('***',ret)
ret = g.send('hello')   #send的效果和next同樣
print('***',ret)

#send 獲取下一個值的效果和next基本一致
#只是在獲取下一個值的時候,給上一yield的位置傳遞一個數據
#使用send的注意事項
    # 第一次使用生成器的時候 是用next獲取下一個值
    # 最後一個yield不能接受外部的值
	
	

  

 

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本質上等於for item in iterable: yield item的縮寫版,對可迭代對象的元素逐一yield

來看一下例子,假設咱們已經編寫好一個斐波那契數列函數
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()的基礎上實現一個函數,調用起始都要記錄日誌(這裏也給我一個啓示,不使用裝飾器,給生成器記錄日誌,給生成器添加裝飾器也不能直接在裝飾器中執行生成器函數,而是要使用for循環迭代執行)
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=' ')
#start
#1 1 2 3 5 end

給生成器添加裝飾器
def init(func):  #在調用被裝飾生成器函數的時候首先用next激活生成器
    def inner(*args,**kwargs):
        g = func(*args,**kwargs)
        next(g)
        return g
    return inner

@init
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total/count


g_avg = averager()
# next(g_avg)   在裝飾器中執行了next方法
print(g_avg.send(10))
print(g_avg.send(30))
print(g_avg.send(5))



如今使用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對象(能夠是生成器,迭代器)***


asyncio.coroutine和yield from
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,接着向後執行代碼。 
協程之間的調度都是由事件循環決定。 

  

 

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)

要運行協程,要用事件循環 
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()

  

 

面試題:
def demo():
    for i in range(4):
        yield i

g=demo()

g1=(i for i in g)  #g1也是個生成器,可是直到調用list(g1)才運行g1裏的語句,而後把調用到g的值,悉數給了list,此時g1做爲生成器,取到頭了,沒值了
g2=(i for i in g1)  #當list(g2)向g1要值得時候,g1沒東西給它

print(list(g1))
print(list(g2))
# [0, 1, 2, 3]   g一、g2均是生成器,經過for能夠把生成器中的值取出來,當所有取出來後,繼續取值就會爲空了
# []


def add(n,i):
    return n+i
 
def test():
    for i in range(4):
         yield i
 
g=test()
for n in [1,10]:
    g=(add(n,i) for i in g)   #先執行 for i in g ,再執行 add函數
 
	print(list(g))

# 生成器的特色是惰性機制,也就是說你找它要,它纔會給你值	
# for n in [1,10]:
#     g=(add(n,i) for i in g)  #先執行 for i in g ,再執行 add函數
# 能夠當作是:
# n = 1: 執行g=(add(n,i) for i in g)
# n = 10:執行g=(add(n,i) for i in g)
# 可是g做爲生成式表達式,這裏說的g是括號外的g,只是一個內存地址,調用不到
# 因此此時 n = 10,而後繼續往下執行 print(list(g))
# 此時開始調用g,可是執行的是 n = 10:執行g=(add(n,i) for i in g)  同時括號裏的g 應該替換爲 當 n=1的時候,執行的g
# 也就是 此時執行的是  n = 10: g=(add(10,i) for i in g=(add(10,i) for i in g))
#注意此處 n的變化
#而後就變爲了  g=(add(10,i) for i in (10,11,12,13))
#最後結果爲 [20,21,22,23]
 
#若是換成
#for n in [1,10,5]:
#    g=(add(n,i) for i in g)
#能夠當作:
# n = 1: 執行g=(add(n,i) for i in test())   
# n = 10:執行g=(add(n,i) for i in (add(n,i) for i in test())) )  
# n = 5:執行g=(add(5,i) for i in (add(n,i) for i in (add(n,i) for i in test())) ))  裏面的n所有爲5
#                                 
#若是將for循環下面得表達式換成g=[add(n,i) for i in g] 結果爲[11, 12, 13, 14]  此時g再也不是生成器而是列表,因此在循環中就直接執行l
# n = 1: 執行g=[add(n,i) for i in g]  g = [1,2,3,4]
# n = 10:執行g=[add(n,i) for i in g]  g = [add(n+i) for i in [1,2,3,4]]  g=10,11,12,13

#若是將for循環的表達式換成 for n in [1,3,10]  或者 for n in [1,11,10] 其結果爲[30, 31, 32, 33]

#若是是 for n in [1,3,6,7,10,11,100,10] 其結果爲[80, 81, 82, 83]  即最後一位數 10 乘以他的索引 +1 就是8再分別加上0,1,2,3,因此最後結果是80,81,82,83,

 

import os
# 當前目錄下有個test1文件夾,裏面有t1.txt和t2.txt,這兩個文件中有一行的內容包含python字符串

def init(func):
    def wrapper(*args, **kwargs):
        g = func(*args, **kwargs)
        next(g)
        return g

    return wrapper


@init
def list_files(target):
    while 1:
        dir_to_search = yield  # 接收 g.send('test1') 發過來的test1
        for top_dir, dir, files in os.walk(dir_to_search):
            # top_dir正在遍歷的這個文件夾的自己的地址 ;
            # dir是一個 list ,內容是該文件夾中全部的目錄的名字(不包括子目錄);
            # files 一樣是 list , 內容是該文件夾中全部的文件(不包括子目錄)
            print(files)   # ['t1', 't2']
            for file in files:
                target.send(os.path.join(top_dir, file))  # target是list_files的參數,這裏指的是opener生成器,向這個生成器發送文件的絕對路徑


@init
def opener(target):
    while 1:
        file = yield  # 這裏接收各個文件的絕對路徑
        fn = open(file)
        print(fn)
        target.send((file, fn))  # 一樣的使參數中的生成器發送文件路徑和文件句柄


@init
def cat(target):
    while 1:
        file, fn = yield  # 接收文件路徑和文件句柄
        for line in fn:  
            print(line)
            target.send((file, line))  # 向grep生成器發送文件路徑和每行的內容


@init
def grep(pattern, target):
    while 1:
        file, line = yield  # 接收文件路徑和每行內容
        if pattern in line: # 當pattern參數(這裏的是'python')在一行的內容中時,將文件名發送給printter生成器
            target.send(file)


@init  # 裝飾器的做用是讓生成器拋出一個值,
def printer():
    while 1:
        file = yield  # 接收文件名
        if file:
            print(file) #打印文件名


g = list_files(opener(cat(grep('python', printer()))))
# 都運行在yield上

g.send('test1')  # 這裏的g是生成器list_files,

  

 

#基於yield併發執行的協程
import time
def consumer():
    '''任務1:接收數據,處理數據'''
    while True:
        print('我是consumer生成器,我在接收數據')
        x=yield

def producer():
    '''任務2:生產數據'''
    g=consumer()
    next(g)
    for i in range(100):
        g.send(i)  # 經過send() 或者next()控制生成器consumer是否運行,用這個特性來分時段執行多個任務
        # 咱們能夠在這個函數中管理多個生成器(對應多個任務),而後經過一些條件(好比說for i in range() 若是i對3取餘,餘數是0執行任務1,餘數是1或2執行任務2),來模擬各個任務的執行時間的不一樣,來控制任務的執行
        print(f'我是producer,我給生成器發送數據,數據是{i}')

start=time.time()
#基於yield保存狀態,實現兩個任務直接來回切換,即併發的效果
#PS:若是每一個任務中都加上打印,那麼明顯地看到兩個任務的打印是你一次我一次,即併發執行的.
producer()

stop=time.time()
print(stop-start) 
相關文章
相關標籤/搜索