Photo by Mikołaj on Unsplash

如何使用 Python 的 Generator ?

如果你的電腦沒有足夠的內存幫你處理大量的數據,也許Python 的 Generator 可以幫助到你

Eric Tsai
6 min readMar 20, 2021

--

為何會需要使用 Generator?

如果你是一名資料科學家,也許你時常會需要處理大量的資料,但如果電腦沒有足夠的內存時,就會容易發生 memory error,以下就舉兩個可能的情境(我瞎想的):

例一

假設今天有一部小說要改編成電影,電影公司想要藉由機器學習的方法預測哪一些章節串連起來可能可以讓觀看者在觀看電影時有足夠的情緒起伏,但一本小說會有多個章節,每個章節又都有大量的文字,若想要一次讀取整本小說進行分析將必須擁有很大的內存記憶體(一般電腦應該是沒有)。

如果今天我們已經建好一個模型,模型的 input 是一段文字,output 會是這段文字情緒起伏的分數,這時我們會需要依序讀取章節內容並進行辨識與儲存結果,然後釋放章節內容所佔的內存,然後再讀取下一個章節內容並進行辨識與儲存結果,直到整個劇本的章節都被我們辨識完畢。透過 generator 我們可以輕鬆寫出以上的處理流程,詳細作法如下:

以下是一本小說的文字檔,假設它很大:)

chapter1.
angry angry angry angry
chapter2.
happy happy happy
chapter3.
hurt hurt
chapter4.
sad
chapterEnd

這麼大的檔案:),一次讀取再進行分析,內存一定不夠,這時我們就可以寫一個 generator ,讓它透過迴圈或 __next__函數,來依序幫我們產出每一行的文字,且產出下一行文字時,若不特別去記下產出結果的話,機器並不會自己記住上一行的產出結果,因此不會讓機器一直消耗內存,詳細作法如下:

底下是一個逐行讀取文字檔的 generator,可以看出只要在定義 function 時,不使用 return 改用 yield 就可以輕鬆寫出一個 generator。

def txt_reader(file_name):
for row in open(file_name, "r"):
yield row

這裡有一個細節要注意,就是當我們要應用 generator 時,一定要將其 assign 一個名稱,如下:

g = txt_reader('./large_file.txt')

不然每次呼叫 txt_reader 時,都會進行初始化,導致你在取值時,只會回傳generator 第一個會產生的結果(在這個例子下會是第一行的文字)

從上方程式碼的展示,我們可以發現 generator 可以透過 __next__ 函數依序產生每一行的文字,但其實 generator 不只一種產生方法,還可以透過 for 迴圈及 list 函數的方式取得產生器產出的結果,原因是 generator 支援 iterator 的操作模式,這裡不做 iterator 的相關解釋,若有興趣可以去看這篇文章:

回到讀取小說章節的例子,我們再來要寫一個 function 來獲取需要的資料

def return_chapter_content():
content_li = []
for row in g:
temp = row
if not 'chapter' in temp:
content_li.append(temp)
return content_li
else:
if temp == 'chapterEnd':
print('No content anymore')
else:
pass

從下方運行程式碼的結果,可以看出 generator 可以輕易地透過呼叫相同的 function,讓機器吐章節的內容給我們,透過這個 function 我們可以很方便的取得章節內容,而不用擔心大量資料一次讀取所造成的 memory error 。

例子二

建構一個無限循環的 function,我想這個部分在資料分析上應該比較少遇到,但還是提一下,也許哪天會用到 :),網路上有一個很常見的例子就是費氏數列,定義如下

F0=0

𝐹1=1

𝐹𝑛=𝐹𝑛−1+𝐹𝑛−2(𝑛≧2)

因為費氏數列是一個無限長度的數列,假設你知道到你會用到很大量費氏數列中的值,但無法確定是多少,這時候就很適合使用 generator ,避免一開始要先產出一個很大的 list 去紀錄費氏數列的數值以備不時之需,所以建立一個產生器,等要用到費氏數列時,再透過 function 呼叫的方式,依序產出序列中的數值會是一個不錯的解決方案,程式碼如下:

def fibonacci():
first = 0
second = 1
yield first
yield second
while True:
after_num = first + second
yield after_num
first = second
second = after_num

但這裡值得一提的是,如果你沒有要用到很大量的費氏數列數值,其實你直接建構一個有限且長度短的費氏數列來使用,會是一個更有效率的方法,邏輯上也很容易理解,就是在不佔太多內存記憶體的狀況下,直接產出完整的數列加以運用,會比用 generator 一次一次產出數值還快的多。

第一次用中文的方式寫文章,希望大家喜歡!!

--

--