1. 程式人生 > >深入淺出學習Python的yield和generator

深入淺出學習Python的yield和generator

生成列 for 下一個 obj 成員 max 獲得 開始 stop

背景

之前走馬觀花接觸過Python協程的概念,這兩天和一個同事聊到了協程,死活想不起來曾經看過的東西,就記得一個yield,概念不清;

所以想捋一捋相關的東西,此篇作為學習的記錄。

Generator

generator(生成器)保存的是算法,可以理解為一個特殊的函數,有叠代(可叠代的對象都有一個__next()__成員方法)的屬性
可以被用作控制循環的叠代行為,做到一邊循環一邊計算;特點是只有被調用的的時候才會生成,能做到不多占用系統的資源。

在我們日常工作過程中接觸最多的generator可能就是Python3.X中的range函數,我們來看一下它和Python2.x中range的用法區別:

# python2
>>> range(10)  
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> type(range(10))
<type 'list'>

# python3
>>> range(10) 
range(0, 10)
>>> type(range(10))
<class 'range'>

>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

如示例,Python2 是直接生成的列表list,而Python3中調用range(10)實際上是生成了一個range類,需要轉換才能生成list。

Python2這樣直接生成列表的機制,在實際使用中容量可能會受到內存的限制。而且如果創建一個包含100萬個元素的列表,而我們又僅僅只需要訪問前面幾個元素,那絕大多數的內存占用就會白白浪費。所以Python3的開發者們才會在這一個小小的函數上下這麽大的功夫。

Generator的使用

創建generator:列表生成式

創建generator方法有很多,最直接最簡單的方法就是使用 列表生成式

>>> L = [x * 2 for x in range(5)]
>>> L
[0, 2, 4, 6, 8]
>>> G = (x * 2 for x in range(5))
>>> G
<generator object <genexpr> at 0x000000000309DD58>

如上例,只要把一個列表生成式的[]改成(),就創建了一個generator

我們可以通過next()函數獲得generator的返回值

>>> next(G)
0
>>> next(G)
2
>>> next(G)
4
>>> next(G)
6
>>> next(G)
8
>>> next(G)
Traceback (most recent call last):
  File "<pyshell#16>", line 1, in <module>
    next(G)
StopIteration

每次調用next(g),就計算出g的下一個元素的值,直到計算到最後一個元素,沒有更多的元素時,拋出StopIteration的錯誤。

當然,這個只是測試,正確的使用方式是使用for循環,在for循環中,會自動遵循叠代規則,每次調用next()函數,而且不需要關心StopIteration的錯誤。

>>> for i in G:
    print(i)
0
2
4
6
8

創建generator: 直接定義生成器函數

# 普通函數
def commom_func(max):
    print("create counter")
    counter = 0
    while counter < max:
        print(counter)
        print('counter increase')
        counter += 1

# 生成器函數
def yield_func(max):
    print("create counter")
    counter = 0
    while counter < max:
        yield counter
        print('counter increase')
        counter += 1
        
# 生成器函數調用
if __name__ == '__main__':
    num = yield_func(5)
    print(next(num))
    print(next(num))
    print(next(num))
---
# 生成器函數調用輸出
create counter
0
counter increase
1
counter increase
2

從上面這個例子可以看出以下幾點:

  1. 在yield_func函數中出現了關鍵字yield,這個函數返回一個生成器(通過第一行輸出可以看出來),用來產生連續的n值,生成器每次只產生一個結果值
  2. 在創造生成器實例的時候,只需要像普通函數一樣調用就可以,但是這個調用卻不會執行這個函數
  3. next()函數將生成器對象作為自己的參數,在第一次調用的時候,他執行了yield_func函數到yield語句,返回產生的值0
  4. 我們重復的調用next()函數,每次他都會從上次被掛起的地方開始執行,直到再次遇到了yield關鍵字
  5. 這就是定義generator的另一種方法。如果一個函數定義中包含yield關鍵字,那麽這個函數就不再是一個普通函數,而是一個generator

下面是一個更直觀的例子

def step_test():
    print('step 1')
    yield 1
    print('step 2')
    yield 2
    print('step 3')
    yield 3

調用該generator時,首先要生成一個generator對象,然後用next()函數不斷獲得下一個返回值:

>>> test = step_test()
>>> next(test)
step 1
1
>>> next(test)
step 2
2
>>> next(test)
step 3
3

總結

  • generator是非常強大的工具,在Python中,可以簡單地把列表生成式改成generator
  • 對於函數改成的generator來說,遇到return語句或者執行到函數體最後一行語句,就是結束generator的指令,for循環隨之結束。
  • 普通函數調用直接返回結果,generator函數的“調用”實際返回一個generator對象:
>>> step_test()
<generator object step_test at 0x0000000001DE5B48>

深入淺出學習Python的yield和generator