引言
你可能聽說過,,帶有 yield 的函數(shù)在 Python 中被稱之為 generator(生成器),又或者都沒關(guān)注過,,Python 中還有個 yield 的存在,。如果你了解過 Python 中的 yield,那你知道何謂 generator 嗎,?
yield 的概念
理解yield 的 generator 概念,,首先以一個常見的編程題目來展示 yield 的概念。
如何生成斐波那契數(shù)列
斐波那契(Fibonacci)數(shù)列是一個非常簡單的遞歸數(shù)列,,除第一個和第二個數(shù)外,,任意一個數(shù)都可由前兩個數(shù)相加得到,。用計算機(jī)程序輸出斐波那契數(shù)列的前 N 個數(shù)是一個非常簡單的問題,有些 Python 基礎(chǔ)的小伙伴都可以輕易寫出如下函數(shù):
第 1 版本:簡單輸出斐波那契數(shù)列前 N 個數(shù)
def createNum(count):
n, a, b = 0, 0, 1
while n < count:
print b
a, b = b, a + b
n = n + 1
createNum(5)
執(zhí)行以上代碼,,我們可以得到如下輸出:
1
1
2
3
5
輸出結(jié)果是沒有問題的,,但是版本 1 中的寫法是直接在 createNum 函數(shù)中用 print 打印數(shù)字會導(dǎo)致該函數(shù)可復(fù)用性較差,因為 createNum 函數(shù)返回 None,,其他函數(shù)無法獲得該函數(shù)生成的數(shù)列,。
要提高 createNum 函數(shù)的可復(fù)用性,最好不要直接打印出數(shù)列,,而是返回一個 List,。以下是 createNum 函數(shù)改寫后的第二個版本:
第 2 版本:輸出斐波那契數(shù)列前 N 個數(shù)
def createNum(count):
n, a, b = 0, 0, 1
L = []
while n < count:
L.append(b)
a, b = b, a + b
n = n + 1
return L
for n in createNum(5):
print n
該版本中 createNum 函數(shù)返回的 List的結(jié)果如下:
1
1
2
3
5
改寫后的 createNum 函數(shù)通過返回 List 能滿足復(fù)用性的要求,但是與此同時也會存在一個明顯的問題是:該函數(shù)在運(yùn)行中占用的內(nèi)存會隨著參數(shù) count 的增大而增大,,如果要控制內(nèi)存占用,,最好不要用 List 來保存中間結(jié)果,而是通過 iterable 對象來迭代,。在每次迭代中返回下一個數(shù)值,,如此:內(nèi)存空間占用很小。因為是直接返回一個 iterable 對象,。
第 3 版本:使用 yield 輸出斐波那契數(shù)列前 N 個數(shù)
def createNum(count):
n, a, b = 0, 0, 1
while n < count:
yield b # 使用 yield
# print(b)
a, b = b, a + b
n = n + 1
for n in createNum(5):
print n
也可以手動調(diào)用 createNum(5) 的 next() 方法(因為 createNum(5) 是一個 generator 對象,,該對象具有 next() 方法),這樣我們就可以更清楚地看到 createNum 的執(zhí)行流程:
第 4 版本:執(zhí)行流程
def createNum(count):
n, a, b = 0, 0, 1
while n < count:
yield b # 使用 yield
# print(b)
a, b = b, a + b
n = n + 1
#使用for循環(huán)來執(zhí)行createNum()函數(shù),它返回一個迭代值,下次迭代從yield語句的下一條語句繼續(xù)執(zhí)行
<!--for n in createNum(5):
print n-->
#使用next方法來執(zhí)行createNum()函數(shù),,generator(生成器)對象具有next()方法
num = createNum(5)
print(next(num))
print(next(num))
print(next(num))
print(next(num))
print(next(num))
print(next(num))
運(yùn)行以上代碼,結(jié)果輸出如下:
1
1
2
3
5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
由輸出結(jié)果可發(fā)現(xiàn)在執(zhí)行第 6 個 print(next(num)) 時拋出一個 StopIteration 的異常,,是因為在第 5 個 print(next(num)) 執(zhí)行完時函數(shù)已經(jīng)結(jié)束,再執(zhí)行第 6 個print(next(num))時,,generator 自動拋出 StopIteration 異常,,表示迭代完成。在 for 循環(huán)里,,無需處理 StopIteration 異常,,循環(huán)會正常結(jié)束。
yield 的作用
簡單地講,,yield 的作用就是把一個函數(shù)變成一個 generator,,帶有 yield 的函數(shù)不再是一個普通函數(shù),,Python 解釋器會將其視為一個 generator,,調(diào)用 createNum(5) 不會執(zhí)行 createNum 函數(shù),而是返回一個 iterable 對象,!
在 for 循環(huán)執(zhí)行時,,每次循環(huán)都會執(zhí)行 createNum 函數(shù)內(nèi)部的代碼,執(zhí)行到 yield b 時,createNum 函數(shù)就會返回一個迭代值,,下次迭代時,,代碼從 yield b 的下一條語句繼續(xù)執(zhí)行,而函數(shù)的本地變量看起來和上次中斷執(zhí)行前是完全一樣的,,于是函數(shù)繼續(xù)執(zhí)行,,直到再次遇到 yield。
yield 使用場景
總結(jié)
一個帶有 yield 的函數(shù)就是一個 generator,,它和普通函數(shù)不同,,生成一個 generator 看起來像函數(shù)調(diào)用,但不會執(zhí)行任何函數(shù)代碼,,直到對其調(diào)用 next()(在 for 循環(huán)中會自動調(diào)用 next())才開始執(zhí)行,。雖然執(zhí)行流程仍按函數(shù)的流程執(zhí)行,但每執(zhí)行到一個 yield 語句就會中斷,,并返回一個迭代值,,下次執(zhí)行時從 yield 的下一個語句繼續(xù)執(zhí)行??雌饋砭秃孟褚粋€函數(shù)在正常執(zhí)行的過程中被 yield 中斷了數(shù)次,,每次中斷都會通過 yield 返回當(dāng)前的迭代值。
yield 的好處是顯而易見的,,把一個函數(shù)改寫為一個 generator 就獲得了迭代能力,,比起用類的實例保存狀態(tài)來計算下一個 next() 的值,不僅代碼簡潔,,而且執(zhí)行流程異常清晰,。