Python 裡的 yield — 讓你簡單、快速瞭解 yield 的概念

學.誌|Chris Kang
5 min readNov 21, 2019

--

當初在 Python 的程式碼裡看到 yield 時,想說就來研究一下吧!應該花不了多少時間,沒想到不知不覺就花了兩個小時,還看的矇矇懂懂。所幸最後瞭解他的概念與應用的時機。

在瞭解程式裡一個功能時,最重要的莫過於問自己––––如果不用這個功能,會怎麼樣?

因此最終,從這個功能所帶來的方便,就能回推該功能的目的,也因此能瞭解如何使用。那麼,yield 的目的是什麼?

為了節省記憶體的使用

這便是 yield 最主要的目的與功能。讓我們來看看下面一個狀況:

my_list = [x*x for x in range(1000000000000000000)]for i in my_list :
... print(i)

可以看到,通常我們在進行遞迴時都會傾向於先把要遞迴的 List 存起來,之後再以 for 迴圈把內容逐步輸出。

但我們可以看到 my_list 裡有非常驚人的數列,因此如果要全部存進記憶體,運行效率就會變的非常的慢。

這時可能有人會說,那為什麼不直接用 range 就好呢?其實 range 在 Python 2 是分成 range 跟 xrange 兩個,range 的生成方式就像上面的 my_list;xrange 才是以 class 的型態運行。

這個問題,最常被注意到的便是爬蟲。有時候短短的幾千條網站,還能靠記憶體來爬;但如果有數十萬個網站,每一個網站都以 list 的方式儲存,記憶體不夠大,程式就會直接崩潰了。

因此 yield 設計來的目的,就是為了單次輸出內容。我們可以把 yield 暫時看成 return,但是這個 return 的功能只有單次。而且,一旦我們的程式執行到 yield 後,程式就會把值丟出,並暫時停止。

直到下一次的遞迴,程式才會從 yield 的下一行開始執行。那哪裡有遞迴呢?沒錯,最常被用到 for 迴圈裡,而且 for 迴圈自帶 next() 的功能。換句話說,for 迴圈會自動在程式內部進行下一輪的遞迴,因而觸動 yield 進行下一輪吐值。而所有能被迭代的物件,都能夠被 yield 製作成生成器。

實際例子:

def foo():
print("start...")
while True:
throw = yield 10
print("throw:",throw)
g = foo()
print(next(g))
print("*"*20)
print(next(g))
# OUTPUT
starting...
10
********************
throw: None
10
  1. 程式開始執行後,因為函式 foo 裡有 yield,因此程式並不會直接運行,而是轉換成生成器(Generator)。
  2. 直到遇到 next() 函式(亦即使用到 while 或是 for 迴圈等),程式就會開始執行,因此開始丟出 start…
  3. 到了 while 迴圈後,遇到了 yield 後面的 10,此時 yield 把 10 丟出去,因此印出了 10。
  4. 這時關鍵來了,丟完 10 之後 yield 就變成不可用(因為在該遞迴回合時只能被使用一次),且函式被凍結在 yield 丟出 10 這一行。
  5. 再來 print 印出一排的 *。
  6. 接下來再遇到下一次的迴圈,此時程式從上次凍結的 yield 這裡,繼續往下一行執行。
  7. 但此時 yield 已經把 10 丟出去了,因此左邊是沒有值的。造成此時的 throw 會沒有賦值,因此會丟出 None。
  8. 再進行下一輪的 while 迴圈,此時又遇到 yield 一次,因此又把 10 再丟出去一次,此時程式又停在這個地方。

如果此時把 next() 換成 send(),就會在 yield 之後被賦予新的值,使 throw 替換成 send() 裡帶來的值。例如:

def foo():
print("start...")
while True:
throw = yield 10
print("throw:",throw)
g = foo()
print(next(g))
print("*"*20)
print(g.send(100))
# OUTPUT
starting...
10
********************
throw: 100
10

之後程式會繼續執行,直到在遇到一次 send() 或是 next()。

再來,就要補充生成器(Generator)的概念。

生成器(Generator)

生成器是一個可以被迭代的對象;但他只能被存取一次,如果再次取用就會得不到任何結果,直到下一輪的迭代。下面舉個例子:

>>> my_list = (x*x for x in range(4))
>>> for i in mylist :
... print(i)
0
1
4
9

PS:這裡注意到,生成器使用的是 (),而非 []。

謝謝你/妳,願意把我的文章閱讀完

如果你喜歡筆者在 Medium 的文章,可以拍個手(Claps),
也歡迎你分享給你覺得有需要的朋友們。

參考資料:

--

--

學.誌|Chris Kang
學.誌|Chris Kang

Written by 學.誌|Chris Kang

嗨!我是 Chris,一位擁有技術背景的獵頭,熱愛解決生活與職涯上的挑戰。專注於產品管理/資料科學/前端開發 / 人生成長,在這條路上,歡迎你找我一起聊聊。歡迎來信合作和交流: chriskang0917@gmail.com