python中的yield生成器詳解


#原創,轉載請先聯系

在學習生成器之前,必須先了解一下迭代器。因為生成器就是一種特殊的迭代器,而且生成器用起來更加優雅。

迭代器的詳解可以參考我的另一篇博文:https://www.cnblogs.com/chichung/p/9537969.html

先說一種比較簡單的生成器,通過例子慢慢來體會什么是生成器。

# 列表生成式
L = [x for x in range(5)]
print(L)

#簡單的生成器
G = (x for x in range(5))  # G就是一個生成器,也是一個迭代器,迭代器也是可迭代對象,所以這個G也可以說是可迭代對象
print(next(G))
print(next(G))
print(next(G))
print(next(G))
print(next(G))

輸出:
[0, 1, 2, 3, 4]
0
1
2
3
4

把列表生成器的[]改為()就變成一個簡單的生成器。由上面的例子,我們大概可以知道,生成器就是一個迭代器,把數據一個一個拿出來,可以減少內存的負擔。

那么,yield又是一個什么東西呢?為什么說他優雅呢?

當我們寫的代碼輸出的結果,想一個一個出來。有兩種常用的方法:

方法1.我們可以創建一個迭代器類,然后把代碼寫進類里,用類來創建一個可迭代對象,然后用next()函數一個一個把結果迭代出來。

方法2.我們可以用代碼函數的合適位置加上yield,這時候這個函數就變成一個生成器了,不需要再創建一個迭代器類,不需要再寫__iter__,__next__方法了。這樣一來不是很方便,很優雅嗎?哈哈哈哈~

口說無憑,下面我們2個方法都做一下,讓你們體會一下:

我們做一個斐波那契的數列生成器。斐波那契數列的第一個數是0,第二個數是1,第三個數是第一、二個數相加,第四個數是第二、三個數相加......

方法1:

class FeiboIterator():
    def __init__(self):
        self.a = 0
        self.b = 1

    def __iter__(self):
        return self

    def __next__(self):
            num = self.a
            self.a,self.b = self.b,self.a+self.b
            return num


iterator = FeiboIterator()
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))

輸出:
0
1 2 3 5 8 13

是不是很麻煩?又要初始化,又要寫__iter__和__next__魔方方法。

方法2:

def feibo():
    a = 0
    b = 1
    while True:
        yield a  # 假如yield后面緊接着一個數據,就會把數據返回,作為next()函數或者for ...in...迭代出的下一個值
        a,b = b,a+b


generator = feibo()

print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))

輸出:
0
1
1
2
3
5
8
13

看!只有6行代碼,是不是很elegant?關於這個程序是怎么運行的?yield是怎么運作的?我們等下就講,現在需要注意幾點:

1.上面代碼的紅色字那里!假如yield后面緊接着一個數據,就會把數據返回,作為next()函數或者for ...in...迭代出的下一個值。

2.假如函數中有yield,就不再是函數。而是一個能返回生成器的函數!注意!是返回,這個函數並不是一個生成器。(修正:這句話發現有錯誤,這個函數也是一個生成器)

3.拿到函數的生成器后,可以和迭代器一樣,用next()函數獲得下一個值。

好了,該來理解一下yield是怎么運作的了!

1.第一次喚醒生成器時,是從函數的起始位置開始,直到遇到yield,就會暫停函數,掛起函數。
2.第二次喚醒生成器時,是從yield斷點處開始,直到又遇到yield。
3.當生成器已經沒有yield,再使用next,則拋StopIteration異常。

然后,我們來理一下上面用yield寫的代碼。

第一次用next()喚醒生成器時,從函數的開頭開始運行,遇到yield a,返回a,然后暫停函數,並記住函數是運行到這個位置的。

第二次喚醒生成器,從yield a斷點處開始,然后a,b開始賦值,while True循環又遇見yield a,返回a,然后暫停函數,並記住函數是運行到這個位置的。

下面喚醒多少次都是這個道理,但是由於這個函數是死循環,所以不會沒有yield,也就不會拋出StopIteration異常。

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

其實yield還能接受值,用send方法進行傳入。代碼體會一下:

def gg():
    i = 1
    while True:
        recv = yield i
        print("接收到一個值:",recv)
        i += 1

generator = gg()

print(next(generator))
print(generator.send("456"))
print(generator.send("789"))

輸出:
1
接收到一個值: 456
2
接收到一個值: 789
3

實現過程和上面的例子一樣。

要懂得的是,yield = a,會返回a。

b = yield,會把yield接收的值賦值給b。


免責聲明!

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



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