在python(本文python環境爲python2.7)中,使用yield關鍵字的函數被稱爲generator(生成器)。故爲了瞭解yield,必然先要了解generator,而瞭解generator之前,我們先要了解一下迭代。
遞歸和迭代
聊迭代之前,我們也順帶簡單瞭解一下遞歸:
1,遞歸:程序調用自身的編程技巧稱爲遞歸
應用案例:求n的階乘
def factorial(n) :
if n == 1 :
return 1 #遞歸結束
return n * factorial(n - 1) #問題規模減1,遞歸調用
2,迭代:迭代是程序中對一組指令(或一定步驟)的重複
應用案例:讀取列表中的每個元素
mylist = [1, 2, 3]
for i in mylist :
print(i)
2.1,可迭代對象是什麼?
如上所示code使用了迭代的方法,而列表mylist是一個可迭代對象。當你建立了一個列表,你可以逐項地讀取這個列表,而這個創建的列表就是一個可迭代對象。
2.2,迭代器是什麼?
迭代器(iterator)是訪問集合內元素的一種方式,提供了一種遍歷類序列對象的方法。對於一般的序列,利用索引從0一直迭代到序列的最後一個元素。對象從集合的第一個元素開始訪問,直到所有的元素都被訪問一遍後結束。對於字典、文件、自定義對象類型等,可以自定義迭代方式,從而實現對這些對象的遍歷。總之,迭起器就是定義了對對象進行遍歷的方式。
而實現了迭代器規範的對象就是迭代器,規範如下:
1,實現了魔法方法 iter(),返回一個迭代對象,這個對象有一個next()方法,
2,實現 next() 方法,返回當前的元素,並指向下一個元素的位置,當前位置已經沒有元素的時候,拋出StopIteration異常。
python for循環的時候,首先對循環對象實現迭代器包裝,返回一個迭代器對象,然後每循環一步,就調用哪個迭代器對象的next方法,循環結束的時候,自動處理了StopIteration這個異常。for循環是對迭代器進行迭代的語法糖。
python中使用iter函數來生成一個迭代器:
>>> t = [1, 2, 3]
>>> it = iter(t)
>>> it.next()
1
生成器和yield
- 生成器是什麼?
生成器也是一種迭代器,但是你只能對其迭代一次。這是因爲它們並沒有把所有的值存在內存中,而是在運行時生成值,這樣能節省大量內存空間並且提高效率。
通過列表生成式,我們可以直接創建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創建一個包含100萬個元素的列表,不僅佔用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。
所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環的過程中不斷推算出後續的元素呢?這樣就不必創建完整的list,從而節省大量的空間。
2,yield是什麼?
yield是python內部的一個關鍵字,內部實現支持了迭代器協議,同時yield內部是一個狀態機,維護着掛起和繼續的狀態,yield關鍵字返回的就是一個生成器。
3,生成器的執行流程
代碼樣例:
>>> def fab(max):
... n, a, b = 0, 0, 1
... while n < max:
... yield b
... a, b = b, a + b
... n = n + 1
...
...
>>> f = fab(5)
>>> f.next()
1
>>> f.next()
1
>>> f.next()
2
>>> f.next()
3
>>> f.next()
5
>>> f.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
通過結果可以看到:
- 當調用生成器函數的時候,函數只是返回了一個生成器對象,並不執行。
- 當next()方法第一次被調用的時候,生成器函數纔開始執行,執行到yield語句處停止,next()方法的返回值就是yield語句處的參數
- 當繼續調用next()方法的時候,函數將接着上一次停止的yield語句處繼續執行,併到下一個yield處停止,如果後面沒有yield就拋出StopIteration異常
4,如何判斷一個函數是否是一個特殊的 generator 函數?可以利用 isgeneratorfunction 判斷:
>>> from inspect import isgeneratorfunction
>>> isgeneratorfunction(fab)
True
結論
一個帶有 yield 的函數就是一個 generator,它和普通函數不同,生成一個 generator 看起來像函數調用,但不會執行任何函數代碼,直到對其調用 next()(在 for 循環中會自動調用 next())纔開始執行。雖然執行流程仍按函數的流程執行,但每執行到一個 yield 語句就會中斷,並返回一個迭代值,下次執行時從 yield 的下一個語句繼續執行。看起來就好像一個函數在正常執行的過程中被 yield 中斷了數次,每次中斷都會通過 yield 返回當前的迭代值。
yield 的好處是顯而易見的,把一個函數改寫爲一個 generator 就獲得了迭代能力,比起用類的實例保存狀態來計算下一個 next() 的值,不僅代碼簡潔,而且執行流程異常清晰。