python中的yield生成器詳解

#原創,轉載請先聯繫html

在學習生成器以前,必須先了解一下迭代器。由於生成器就是一種特殊的迭代器,並且生成器用起來更加優雅。函數

迭代器的詳解能夠參考個人另外一篇博文:http://www.javashuo.com/article/p-sxtfliuk-do.html學習

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

# 列表生成式
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

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

那麼,yield又是一個什麼東西呢?爲何說他優雅呢?htm

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

方法1.咱們能夠建立一個迭代器類,而後把代碼寫進類裏,用類來建立一個可迭代對象,而後用next()函數一個一個把結果迭代出來。blog

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

口說無憑,下面咱們2個方法都作一下,讓大家體會一下:get

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

方法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。

相關文章
相關標籤/搜索