看了大佬的博客很快就懂了。
這是原博客鏈接: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))