Python中Generators教程

轉載至:https://www.bytelang.com/article/content/NQbmUaRIXyA=python

要想建立一個iterator,必須實現一個有__iter__()和__next__()方法的類,類要可以跟蹤內部狀態而且在沒有元素返回的時候引起StopIteration異常.express

這個過程很繁瑣並且違反直覺.Generator可以解決這個問題.函數

python generator是一個簡單的建立iterator的途徑.前面講的那些繁瑣的步驟均可以被generator自動完成.工具

簡單來講,generator是一個可以返回迭代器對象的函數.oop

怎樣建立一個python generator?

就像建立一個函數同樣簡單,只不過不使用return 聲明,而是使用yield聲明.spa

若是一個函數至少包含一個yield聲明(固然它也能夠包含其餘yield或return),那麼它就是一個generator. 日誌

yield和return都會讓函數返回一些東西,區別在於,return聲明完全結束一個函數,而yield聲明是暫停函數,保存它的全部狀態,而且後續被調用後會繼續執行.code

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=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=

 

有循環的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))

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

相關文章
相關標籤/搜索