0%

Generator的概念與如何使用

generator本身也是iterator,和iterator不同的地方在於generator會透過function宣告的方式,但有別於一般的function直接return回傳值,generator的function會在”有需要”的時候,使用yield來回傳值。為什麼稱為”有需要”的時候呢?因為generator在每次呼叫next()回傳值後,會記下所有的資料並保留最後一次執行狀態,直到下一次呼叫next()才會再繼續執行未結束的狀態。我們可以看一下下面這個範例:

1
2
3
def transform(data):    
for char in data:
yield chr(ord(char) + 1) # 透過yield回傳值後留存狀態

在每一次呼叫next()時,generator才會開始執行,並且每次執行到yield回傳值後停止,並保留執行狀態直到下一次呼叫next()才繼續執行。當generator終止後呼叫next()則會發生StopIteration。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> char = transform('python')
>>> next(char)
q
>>> next(char)
z
>>> next(char)
i
>>> next(char)
p
>>> next(char)
p
>>> next(char)
o
>>> next(char)
Traceback (most recent call last):
File <stdin>, line 1, in
next(char)StopIteration

也可以使用for loop來呼叫generator

1
2
3
4
5
6
7
8
9
for char in transform('python'):     
print(char)

q
z
u
i
p
o

使用generator最大的好處在於可以有效的處理一次性使用大量使用Memory的情況,將要一次性處理的過程拆成generator分次執行。例如當需要讀取很大的檔案處理時(甚至是多個很大的檔案)。而且使用generator可以降低iterator在loop的維度,讓程式碼可以更加的乾淨。

1
2
3
4
5
6
7
8
9
def get_line_title(file_name):    
for file in file_name:
for line in open(file, mode='r', encoding='utf-8'):
if line.istitle():
yield line

>>> lines = get_line_title(file_name)
>>> for line in lines:
>>> print(line)

上面的範例會產生generator並逐次判斷每一行內容,並回傳所有的標題。其中會用到兩個for loop和一個邏輯判斷,我們可以改寫成下面這樣,把讀取的部份和邏輯判斷的部份拆開來。

1
2
3
4
5
6
7
8
9
10
def get_line(files):    
for file in files:
for line in open(file, mode='r', encoding='utf-8'): yield line

def get_title(lines):
return (line for line in lines if line.istitle())

>>> all_line = get_line(file_name)
>>> for title in get_title(all_line):
>>> print(title)

如果善用generator可以有效的管理Memory的使用,也可以讓程式碼更加的有可讀性!

參考資料:
Python Docs - Generators
Iterators & Generators
Introduction to Python Generators