Python中yield函數淺析


帶有yield的函數在Python中被稱之為generator(生成器),下面我們將使用斐波那契數列來舉例說明下該函數:(環境是在Python3.x下)

 如何生成斐波那契數列:

  斐波那契(Fibonacci)數列是一個簡單的遞歸數列,除第一個數和第二個數外,任意一個數都可由前兩個數相加得到。用計算機程序輸出斐波那契數列的前N個數是一個非常簡單的問題:

  • 版本一:簡單輸出斐波那契數列前N個數
# -*- coding: utf-8 -*-
# Time    : 2019/5/28 14:25
# Author  : Eric
# FileName: yield使用淺析.py
# Software: PyCharm
#-----------------------------------------------------------------------------------------------------------------------
 
def fab(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
#執行fab(10),我們可以得到如下的輸出:
print(fab(10))
1
1
2
3
5
8
13
21
34
55 None

結果是沒有問題,但是有經驗的開發者會指出,直接在fab函數中有print打印數字會導致該函數可復用性較差,因為fab函數返回None,其他函數無法獲得該函數生成的數列。

要提高fab函數的復用性,最好不要直接打印出數列,而是返回一個列表(list)。以下是fab函數改寫后的第二個版:

  • 版本二:輸出斐波那契數列前N個數
def fab(max):
    n, a, b = 0, 0, 1
    L = []
    while n < max:
        L.append(b)
        a, b = b, a + b
        n = n + 1
    return L

#可以使用如下方式打印出fab函數返回的List:
for n in fab(10):
    print(n)

1
1
2
3
5
8
13
21
34
55

改寫后的fab函數通過返回List能滿足復用性的要求,但是更有經驗的開發者會指出,該函數在運行中占用的內存會隨着參數max的增大而增大,如果要控制內存占用,最好不用要List來保存中間結果。

  • 版本三: 使用創建類的的方法來實現
class Fab(object):

    def __init__(self,max):
        self.max = max
        self.n, self.a, self.b = 0, 0, 1

    def __iter__(self):
        return self

    def __next__(self):
        if self.n < self.max:
            r = self.b
            self.a, self.b = self.b, self.a + self.b
            self.n = self.n + 1
            return r
        raise StopIteration()
#Fab類通過__next__()不斷返回數列的下一個數,內存占用始終為常數:
for n in Fab(5):
    print(n)
1
1
2
3
5

然而,使用class改寫的這個版本,代碼遠遠沒有第一版的fab函數來得簡潔。如果我們想要保持第一版fab函數的簡潔性,同時又要獲得iterable的效果,yield就派上用場了。

  • 版本四:使用yield的第四版

 

def fab(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1

#調用第四版的fab和第二版的fab完全一致:
for n in fab(5):
    print(n)
1
1
2
3
5

 

  簡單地講,yield的作用就是把一個函數變成一個generator,帶有yield的函數不再是一個普通的函數,Python解釋器會將其視為一個generator,調用fab(5)不會執行fab函數,而是返回一個iterable對象!在for循環執行時,每次循環都會執行fab函數內部的代碼,執行到yield時,返回函數就會返回一個迭代值,下次迭代時,代碼從yield的下一條語句繼續執行,而函數的本地變量看起來和上次中斷執行前是完全一樣的,於是函數繼續執行,直到再次遇到yield。

  yield的好處是顯而易見的,把一個函數改寫為一個generator就獲得了迭代的能力,比起用類的實例保存狀態來計算下一個next()的值,不僅代碼簡介,而且執行流程異常清晰。

  如何判斷一個函數是否是一個特殊的generator函數?可以利用isgeneratorfunction判斷:

 

from inspect import isgeneratorfunction
print(isgeneratorfunction(fab))

True

 

要注意區分fab和fab(5),fab是一個generatorfunction,而fab(5)是調用fab返回的一個generator,好比類的定義和類的實例的區別:

類的定義和類的實例:

import types
print(isinstance(fab,types.GeneratorType))
print(isinstance(fab(5),types.GeneratorType))

False
True

fab是無法迭代的,而fab(5)是可迭代的:

from collections import Iterable
print(isinstance(fab,Iterable))
print(isinstance(fab(5),Iterable))
False
True

  

return的作用

  在一個generator function中,如果沒有return,則默認執行至函數完畢,如果在執行過程中return,則直接拋出StopIteration終止迭代。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM