python中yield的用法以及和yield from的區別


看了大佬的博客很快就懂了。

這是原博客鏈接:https://blog.csdn.net/mieleizhi0522/article/details/82142856

由於最近接觸了酷q機器人,搭建好了環境,配合NoneBot可以通過python代碼自己寫機器人功能。

NoneBot是基於asyncio的,所以先通過yield來學習一點python協程方面的知識。

 

yield

首先,先可以把yield看成“return”,return什么意思大家都知道吧,就是代表在程序中返回某個值,return所在的當前函數就停住了,不能往下再運行下去了。

然后先看下面代碼:

def foo(): print("starting...") while True: res = yield 4
        print("res:", res) g = foo() print(next(g)) print("*" * 20) print(next(g))

這段代碼輸出的是如下:

starting... 4
******************** res: None 4

所以我們來一步一步分析程序:

1.程序開始執行之后,在foo函數中有yield關鍵字,所以foo函數並不會真的執行,而是先得到了一個生成器g(相當於我們創造出了一個對象)。

2.直到調用了next方法,這里我們先要知道在python中,next()返回的是迭代器的下一個項目,所以后面我們調用了next(g)方法,foo函數正式開始執行,先執行了foo函數中的print方法,然后就進入了while循環。

3.程序遇到yield關鍵字,然后我們現在把yield當成return,所以返回了一個4之后,程序就結束了,后面的給res賦值的操作並沒有執行。此時的話第一個print(next(g))就執行完成了,所以輸出了前兩行的結果。

4.程序執行print("*"*20),輸出了20個*。

5.又開始執行下面的print(next(g)),這個時候和上面那個差不多,不過不同的是,這個時候是從剛才那個next程序停止的地方開始執行的,也就是接着上面return之后的操作,因為while函數里面其實是先return,再賦值給res,上面在return終止之后,就沒有賦值給res,所以輸出的就是res:None。

6.程序會繼續在while里執行,又一次碰到yield,這個時候同樣return出4,然后程序停止,print函數輸出的4就是這次return的4。

 

所以到這里你可能就明白了yield和return的關系和區別了,帶yield的函數是一個生成器,而不是一個函數,這個生成器有一個函數就是next函數,next就相當於"下一步"生成哪個書,這一次的next開始的地方是接着上一次的next停止的地方執行的,所以調用next的時候,生成器並不會從foo函數開始執行,只是接着上一步停止的地方開始,然后遇到yield后,return出要生成的數,然后到這步就停止結束了。

 

再看一下下面這個例子:

def foo(): print("starting...") while True: res = yield 4
        print("res:", res) g = foo() print(next(g)) print("*" * 20) print(g.send(7))

這里調用了一個g.send(7)函數,然后輸出結果是:

starting... 4
******************** res: 7
4

 

先大致說一下send函數的概念:此時你應該注意到上面那個的紫色的字,還有上面那個res的值為什么是None,這個變成了7,到底為什么,這是因為,send是發送一個參數給res的,因為上面講到,return的時候,並沒有把4賦值給res,下次執行的時候只好繼續執行賦值操作,只好賦值為None了,而如果用send的話,開始執行的時候,先接着上一次(return 4之后)執行,先把7賦值給了res,然后執行next的作用,遇見下一回的yield,return出結果后結束。

 

5.程序執行g.send(7),程序會從yield關鍵字那一行繼續向下運行,send會把7這個值賦值給res變量

6.由於send方法中包含next()方法,所以程序會繼續向下運行執行print方法,然后再次進入while循環

7.程序執行再次遇到yield關鍵字,yield會返回后面的值后,程序再次暫停,直到再次調用next方法或send方法。

這就結束了,說一下,為什么用這個生成器,是因為如果用List的話,會占用更大的空間,比如說取0,1,2,3,4,5,6............1000

你可能會這樣:

for n in range(1000): a=n

這個時候range(1000)就默認生成一個含有1000個數的list了,所以很占內存。

這個時候你可以用剛才的yield組合成生成器進行實現,也可以用xrange(1000)這個生成器實現

yield組合:

def foo(num): print("starting...") while num<10: num=num+1
        yield num for n in foo(0): print(n)

輸出:

starting... 1
2
3
4
5
6
7
8
9
10

 

 

yield from

關於區別可以看這篇博客:https://www.cnblogs.com/cnkai/p/7514828.html

前面的yield都是單一層次的生成器,並沒有嵌套,如果是多個生成器嵌套會怎么樣呢,下面是一個例子:

def fun_inner(): i = 0 while True: i = yield i def fun_outer(): a = 0 b = 1 inner = fun_inner() inner.send(None) while True: a = inner.send(b) b = yield a if __name__ == '__main__': outer = fun_outer() outer.send(None) for i in range(5): print(outer.send(i))

這個例子在fun_outer()函數里面先調用了一次fun_inner(),這是一個容器,再嵌套外面的yield a,然后在main中調用了fun_outer(),給其send一個i進去,在兩層嵌套的情況下,值的傳遞方式是,先把值傳遞給外層生成器,外層生成器再將值傳遞給外層生成器,內層生成器在將值反向傳遞給外層生成器,最終yield出結果。如果嵌套的層次更多,傳遞將會越麻煩。

輸出結果如下:

0 1
2
3
4

然后我們用yield from來實現,代碼明顯少了很多。

def fun_inner(): i = 0 while True: i = yield i def fun_outer(): # a = 0
    # b = 1
    # inner = fun_inner()
    # inner.send(None)
    # while True:
    # a = inner.send(b)
    # b = yield a
    yield from fun_inner() if __name__ == '__main__': outer = fun_outer() outer.send(None) for i in range(5): print(outer.send(i))

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM