深入淺出學習Python的yield和generator
背景
之前走馬觀花接觸過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
從上面這個例子可以看出以下幾點:
- 在yield_func函數中出現了關鍵字yield,這個函數返回一個生成器(通過第一行輸出可以看出來),用來產生連續的n值,生成器每次只產生一個結果值
- 在創造生成器實例的時候,只需要像普通函數一樣調用就可以,但是這個調用卻不會執行這個函數
next()函數將生成器對象作為自己的參數
,在第一次調用的時候,他執行了yield_func函數到yield語句,返回產生的值0- 我們重復的調用next()函數,每次他都會從上次被掛起的地方開始執行,直到再次遇到了yield關鍵字
這就是定義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