Python yield 函數功能


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


免責聲明!

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



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