python中有一個非常有用的語法叫做生成器,所利用到的關鍵字就是yield。有效利用生成器這個工具可以有效地節約系統資源,避免不必要的內存占用。
一段代碼
def test_dict_sort(): _dict = {'b':2,'c':1,'a':3} print('abcd') for x in [1,2,3]: a = yield x print('a:',a) print(sorted(_dict.items(), key = lambda x:x[1]))
if __name__ == '__main__': a = test_dict_sort() print("_main_", a) print(a.send(None)) #等價a.__next__(),第一次調用的時候send的參數必須為None print(a.send(1)) print(a.send(2)) # print(a.__next__())
輸出結果:
_main_ <generator object test_dict_sort at 0x000000000567BFC0> abcd 1 a: 1 2 a: 2 3 a: None [('c', 1), ('b', 2), ('a', 3)]
這段代碼很短,但是詮釋了yield關鍵字的核心用法,即逐個生成。在這里獲取了生成器產生的值,即1,2,3。分別由next函數和send()函數獲得,這兩個函數的區別我們后面會詳細闡述。
關於__next__函數,這里先說明一下,我們可以利用__next__()這個函數持續獲取符合test_dict_sort函數規則的數。這段代碼如下所示:
sent函數
這里特別強調了sent函數,因為sent函數沒有那么直觀。__next__函數很好理解,就是從上一個終止點開始,到下一個yield結束,返回值就是yield表達式的值。
例如在初始的那段代碼里:
def test_dict_sort(): _dict = {'b':2,'c':1,'a':3} print('abcd') for x in [1,2,3]: a = yield x print('a:',a) print(sorted(_dict.items(), key = lambda x:x[1]))
第一次調用__next__函數的時候,我們從test_dict_sort的起點開始,然后在yield處結束,需要注意的是,賦值語句不會調用,此處yield i和含義和return差不多。但是第二次調用__next__函數的時候,就會直接從上一個yield的結束處開始,也就是先執行賦值語句,然后輸出字符串,進入下一個循環,直到下一個yield或者生成器結束再次看初始的那段代碼,可以發現第二次調用的時候沒有選擇使用__next__函數,而是使用了一個sent()函數。這里就需要注意,sent()函數的用法和__next__函數不太一樣。sent()函數只能從yield之后開始,到下一個yield結束。這也就意味着第一次調用必須使用__next__函數。
sent()函數最重要的作用在於它可以給yield對應的賦值語句賦值,比如上面那一段代碼中的
a=yield x
如果調用__next()__函數,那么x=None。但是如果調用sent(1),那么a=1。除了上述將的兩個特征以外,sent和next並沒有什么區別,sent函數也會返回yield表達式對應的值
心得:
閱讀別人的python源碼時碰到了這個yield這個關鍵字,各種搜索終於搞懂了,在此做一下總結:
1、通常的for...in...循環中,in后面是一個數組,這個數組就是一個可迭代對象,類似的還有鏈表,字符串,文件。它可以是mylist = [1, 2, 3],也可以是mylist = [x*x for x in range(3)]。
它的缺陷是所有數據都在內存中,如果有海量數據的話將會非常耗內存。
2、生成器是可以迭代的,但只可以讀取它一次。因為用的時候才生成。比如 mygenerator = (x*x for x in range(3)),注意這里用到了(),它就不是數組,而上面的例子是[]。
3、我理解的生成器(generator)能夠迭代的關鍵是它有一個next()方法,工作原理就是通過重復調用next()方法,直到捕獲一個異常。可以用上面的mygenerator測試。
4、帶有 yield 的函數不再是一個普通函數,而是一個生成器generator,可用於迭代,工作原理同上。
5、yield 是一個類似 return 的關鍵字,迭代一次遇到yield時就返回yield后面(右邊)的值。重點是:下一次迭代時,從上一次迭代遇到的yield(yield相當於return )后面的代碼(如果有賦值操作則將send傳過來的參數賦值給變量)開始執行。
6、簡要理解:yield就是 return 返回一個值,並且記住這個返回的位置,下次迭代就從這個位置后(如果有賦值操作則將send傳過來的參數賦值給變量,否則執行下一行)開始。
7、帶有yield的函數不僅僅只用於for循環中,而且可用於某個函數的參數,只要這個函數的參數允許迭代參數。比如array.extend函數,它的原型是array.extend(iterable)。
8、send(msg)與next()的區別在於send可以傳遞參數給yield表達式,這時傳遞的參數會作為yield表達式的值,而yield的參數是返回給調用者的值。——換句話說,就是send可以強行修改上一個yield表達式值。比如函數中有一個yield賦值,a = yield 5,第一次迭代到這里會返回5,a還沒有賦值。第二次迭代時,使用.send(10),那么,就是強行修改yield 5表達式的值為10,本來是5的,那么a=10
9、send(msg)與next()都有返回值,它們的返回值是當前迭代遇到yield時,yield后面表達式的值,其實就是當前迭代中yield后面的參數。
10、第一次調用時必須先next()或send(None),否則會報錯,send后之所以為None是因為這時候沒有上一個yield(根據第8條)。可以認為,next()等同於send(None)。
Reference
1. https://www.cnblogs.com/zhenlingcn/p/8337788.html
2. https://blog.csdn.net/qq_36330643/article/details/78247070