轉自:https://www.bytelang.com/article/content/NQbmUaRIXyA=python
要想建立一個iterator,必須實現一個有__iter__()和__next__()方法的類,類要可以跟蹤內部狀態而且在沒有元素返回的時候引起StopIteration異常.express
這個過程很繁瑣並且違反直覺.Generator可以解決這個問題.函數
python generator是一個簡單的建立iterator的途徑.前面講的那些繁瑣的步驟均可以被generator自動完成.工具
簡單來講,generator是一個可以返回迭代器對象的函數.oop
就像建立一個函數同樣簡單,只不過不使用return 聲明,而是使用yield聲明.spa
若是一個函數至少包含一個yield聲明(固然它也能夠包含其餘yield或return),那麼它就是一個generator. 日誌
yield和return都會讓函數返回一些東西,區別在於,return聲明完全結束一個函數,而yield聲明是暫停函數,保存它的全部狀態,而且後續被調用後會繼續執行.code
下面這個例子說明上述所有要點,咱們有一個名爲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=ip
有趣的是,在這個例子裏變量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=
上面的例子沒有實際的應用意義,咱們只是爲了探究背後原理。
一般來講,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等。
使用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表達式能夠在函數內部使用。當這樣使用的時候,圓括號能夠丟棄。
相對於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自動跟蹤實現細節,所以更加清晰、簡潔。
一個函數返回一個序列(sequence)的時候,會在內存裏面把這個序列構建好再返回。若是這個序列包含不少數據的話,就過猶不及了。
而若是序列是以generator方式實現的,就是內存友好的,由於他每次只產生一個item。
generator是一個很棒的表示無限數據流的工具。無限數據流不能被保存在內存裏面,而且由於generator每次產生一個item,它就能夠表示無限數據流。
下面的代碼能夠產生全部的奇數
def all_even(): n = 0 while True: yield n n += 2
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))
這個流水線既高效又易讀,而且看起來很酷!:)