轉載至:https://www.bytelang.com/article/content/NQbmUaRIXyA=
要想創建一個iterator,必須實現一個有__iter__()和__next__()方法的類,類要能夠跟蹤內部狀態並且在沒有元素返回的時候引發StopIteration異常.
這個過程很繁瑣而且違反直覺.Generator能夠解決這個問題.
python generator是一個簡單的創建iterator的途徑.前面講的那些繁瑣的步驟都可以被generator自動完成.
簡單來說,generator是一個能夠返回迭代器對象的函數.
怎樣創建一個python generator?
就像創建一個函數一樣簡單,只不過不使用return 聲明,而是使用yield聲明.
如果一個函數至少包含一個yield聲明(當然它也可以包含其他yield或return),那么它就是一個generator.
yield和return都會讓函數返回一些東西,區別在於,return聲明徹底結束一個函數,而yield聲明是暫停函數,保存它的所有狀態,並且后續被調用后會繼續執行.
generator函數和普通函數的區別
- generator函數包含一個以上的yield聲明
- generator函數被調用的時候,會返回一個iterator對象,但是函數並不會立即開始執行
- __iter__()和__next__()方法被自動實現,所以可以使用next()函數對返回的此iterator對象進行迭代
- 一旦一個generator 執行到yield語句,generator函數暫停,程序控制流被轉移到調用方
- 在對generator的連續調用之間,generator的本地變量和狀態會被保存
- 最終,generator函數終止,再調用generator會引發StopIteration異常
下面這個例子說明上述全部要點,我們有一個名為my_gen()的函數,它帶有一些yield聲明.
# A simple generator function def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n
在線實例:https://www.bytelang.com/o/s/c/nDeJ2dm7FUo=
有趣的是,在這個例子里變量n在每次調用之間都被記住了。和一般函數不同的是,在函數yield之后本地變量沒有被銷毀,而且,generator對象只能被這樣迭代一次。
要想重復上面的過程,需要類似 a = my_gen() 這樣創建另一個generator對象,並對其使用next方法迭代。
注意
:我們可以對generator對象直接使用for循環。
這是因為一個for循環接收一個iterator對象,且使用next()函數迭代它,當遇到StopIteration異常的時候自動停止。
# A simple generator function def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n # Using for loop # Output: # This is printed first # 1 # This is printed second # 2 # This is printed at last # 3 for item in my_gen(): print(item)
在線示例:https://www.bytelang.com/o/s/c/3py5nUg_WVI=
有循環的python generator
上面的例子沒有實際的應用意義,我們只是為了探究背后原理。
通常來說,generator都是和循環結合實現的,且這個循環帶有一個終止條件。
我們來看一個reverse一個字符串的例子
def rev_str(my_str): length = len(my_str) for i in range(length - 1,-1,-1): yield my_str[i] # For loop to reverse the string # Output: # o # l # l # e # h for char in rev_str("hello"): print(char)
在線示例:https://www.bytelang.com/o/s/c/_rs3yQEbIhE=
我們在for循環里面使用range()函數來獲取反向順序的index。
generator除了可以應用於string,還可以應用於其它類型的iterator,例如list,tuple等。
python generator 表達式
使用generator表達式可以很容易地創建簡單的generator。
就像lambda函數可以創建匿名函數一樣,generator函數創建一個匿名generator函數。
generator表達式的語法類似於python的list comprehension,只是方括號被替換為了圓括號而已。
list comprehension和generator表達式的主要區別在於,前者產生全部的list,后者每次僅產生一項。
它們有些懶惰,僅在接到請求的時候才會產生輸出。因此,generator表達式比list comprehension更加節省內存。
# Initialize the list my_list = [1, 3, 6, 10] # square each term using list comprehension # Output: [1, 9, 36, 100] [x**2 for x in my_list] # same thing can be done using generator expression # Output: <generator object <genexpr> at 0x0000000002EBDAF8> (x**2 for x in my_list)
在線示例:https://www.bytelang.com/o/s/c/BgIb7R1NCls=
上面的例子中,generator表達式沒有立即產生需要的結果,而是在需要產生item的時候返回一個generator對象。
# Intialize the list my_list = [1, 3, 6, 10] a = (x**2 for x in my_list) # Output: 1 print(next(a)) # Output: 9 print(next(a)) # Output: 36 print(next(a)) # Output: 100 print(next(a)) # Output: StopIteration next(a)
在線示例:https://www.bytelang.com/o/s/c/p1^6fITXP5A=
generator表達式可以在函數內部使用。當這樣使用的時候,圓括號可以丟棄。
python里為什么要使用generator?
1.容易實現
相對於iterator類來說,generator的實現清晰、簡潔。下面是用iterator實現一個2的指數函數
class PowTwo: def __init__(self, max = 0): self.max = max def __iter__(self): self.n = 0 return self def __next__(self): if self.n > self.max: raise StopIteration result = 2 ** self.n self.n += 1 return result
generator這樣實現
def PowTwoGen(max = 0): n = 0 while n < max: yield 2 ** n n += 1
因為generator自動跟蹤實現細節,因此更加清晰、簡潔。
2.節省內存
一個函數返回一個序列(sequence)的時候,會在內存里面把這個序列構建好再返回。如果這個序列包含很多數據的話,就過猶不及了。
而如果序列是以generator方式實現的,就是內存友好的,因為他每次只產生一個item。
3.代表無限的stream
generator是一個很棒的表示無限數據流的工具。無限數據流不能被保存在內存里面,並且因為generator每次產生一個item,它就可以表示無限數據流。
下面的代碼可以產生所有的奇數
def all_even(): n = 0 while True: yield n n += 2
4.generator流水線(pipeline)
generator可以對一系列操作執行流水線操作。
假設我們有一個快餐連鎖店的日志。日志的第四列是每小時售出的披薩數量,我們想對近5年的這一數據進行求和。
假設所有數據都是字符,不可用的數據都以"N/A"表示,使用generator可以這樣實現
with open('sells.log') as file: pizza_col = (line[3] for line in file) per_hour = (int(x) for x in pizza_col if x != 'N/A') print("Total pizzas sold = ",sum(per_hour))
這個流水線既高效又易讀,並且看起來很酷!:)