Python中Generators教程


轉載至: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)) 

這個流水線既高效又易讀,並且看起來很酷!:)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM