python yield淺析

在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

  1. 生成器是什麼?

生成器也是一種迭代器,但是你只能對其迭代一次。這是因爲它們並沒有把所有的值存在內存中,而是在運行時生成值,這樣能節省大量內存空間並且提高效率。

通過列表生成式,我們可以直接創建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創建一個包含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() 的值,不僅代碼簡潔,而且執行流程異常清晰。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章