測開之資料型別· 第4篇《迭代器、生成器》

清菡發表於2020-12-19

堅持原創輸出,點選藍字關注我吧

作者:清菡
部落格:oschina、雲+社群、知乎等各大平臺都有。

由於微信公眾號推送改為了資訊流的形式,防止走丟,請給加個星標 ⭐,你就可以第一時間接收到本公眾號的推送!

文章總覽圖

目錄

  • 一、迭代器
    • 1.迭代協議
    • 2.什麼是迭代器呢?
    • 3.可迭代物件
    • 4.這個是可迭代物件和迭代器的區別
  • 二、生成器
    • 1.什麼是迭代操作?
    • 2.生成器和迭代器有什麼不同呢?
    • 3.生成器比迭代器多了 3 種方法
    • 4.為什麼生成器有的方法,迭代器沒有?
    • 5.資料傳送到生成器,在哪個地方呢?
  • 三、系列推薦

一、迭代器

1.迭代協議

一種是包含iter方法的,另一種是包含getitem方法的(比如str物件就沒有iter方法,但是一樣能夠迭代),只要物件中包含了這兩種方法的任意一種,那麼這個物件就可以進行迭代操作,也就是實現了迭代協議。

2.什麼是迭代器呢?

生成器是迭代器的一種。迭代器的範圍比生成器更廣。只要可以通過next(),從裡面一個一個往外面取值,都被稱為迭代器。

關於要建立一個迭代器物件,那麼內部要實現一個迭代器的協議。

2.1 迭代器協議

  • 實現了迭代器協議的物件(實現方式:物件內部定義了一個iter()方法)。
  • 物件實現了__next__方法。
  • __next__方法返回了某個數值(當然一般情況下,我們需要的是返回這個物件的特定的數字,並且按照一定的順序進行依次返回)。
  • __next__方法需要在值取完的時候,丟擲stopiteration的錯誤資訊。

3.可迭代物件

有個東西需要區分,一個是迭代器,一個是可迭代物件。

只要內部實現了迭代協議的就是一個可迭代物件(可迭代物件可以進行相關的迭代操作,比如for迴圈,map函式等)。

可以用 for 迴圈進行遍歷的,那麼都是可迭代物件。可迭代物件不一定是迭代器,迭代器是在可迭代基礎上,它內部要首先定義一個__next_方法。

迭代器內部實現了一個__next_方法,實現了這個方法之後,通過__next_這個函式才可以對這個迭代器進行一個取值。

還有個iter()方法,這個方法可將可迭代物件轉換成一個迭代器。

yieldreturn是 2 個東西。yield只是暫停那個生成器函式。yield可以從生成器裡面生成一個內容。

列表可以進行 for 迴圈,可以進行 for 迴圈遍歷,它就是個可迭代物件。 列表是可以通過 for 迴圈遍歷的,但是它不是迭代器。

迭代器是可以通過next()進行取值的。

生成器也是迭代器,生成器是可以通過next()去取值。那麼,生成器它是迭代器的一種,是屬於迭代器的。

你看,報錯了:

# 列表
# 可迭代物件:可以for迴圈遍歷的都是可迭代物件
li = [1,2,3,4]
next(li)
print(next(li))

提示:列表它不是一個迭代器。

不是個迭代器,不能通過這個去取值。要把一個可迭代物件轉換成一個迭代器的話,通過iter()這個函式把可迭代物件放進去,它能夠返回一個迭代器。

你看,這樣就能獲取到了:

# 列表
# 可迭代物件:可以for迴圈遍歷的都是可迭代物件
li = [1,2,3,4]
li1 = iter(li)
print(next(li1))
print(next(li1))

通過iter()這個函式,來處理某個物件,它實際上相當於觸發這個物件內部的一個__iter__這個方法。

我們們看看list()的原始碼:

通過iter()這個函式把物件li傳進去的時候,它會觸發li這個物件對應的__iter_這個方法。

如果通過next()去取值,把li1這個物件傳進去的時候,實際上是觸發這個物件的__next__方法。

它的類裡面只有這個__iter__方法。

迭代器可以通過__next__取值。迭代器內部實現了__next__方法。

迭代器內部實現了 __iter__方法之外,還實現了__next__ 方法。

4.這個是可迭代物件和迭代器的區別

二、生成器

生成器是迭代器的一種。

迭代器是在可迭代物件的基礎上實現了__iter_方法。迭代器和生成器都可以支援迭代操作。

1.什麼是迭代操作?

for 迴圈。

2.生成器和迭代器有什麼不同呢?

生成器是迭代器的一種。 剛才用起來的時候好像沒有什麼區別,列印下這個型別看看。

可以看到,它返回的是個列表迭代器物件:

這個是生成器物件:

li1 = iter(li)這個是可迭代物件。然後通過iter()轉換成一個迭代器。

3.生成器比迭代器多了 3 種方法

send()方法 傳送資料
close() 方法 關閉生成器
throw() 方法 使用的 throw 指令丟擲錯誤

生成器是有send這個方法的,迭代器是沒有的。

例如,前面有個生成器叫做tu

# () 生成器表示式
tu = (i for i in range(1000))#生成器物件
print(tu)

tu可以呼叫send()這個方法,可以與生成器進行互動,可將資料傳輸到生成器裡面。

4.為什麼生成器有的方法,迭代器沒有?

舉個例子:

生成器是迭代器的一種。

例如定義了一個父類,再有個子類,父類建立出一個物件,子類建立出一個物件。子類有自己的方法。父類建立的出來的物件裡面,肯定沒有子類物件裡面的方法。 子類裡面有的方法,父類裡面沒有。

迭代器就是“父類”。生成器就是“子類”。

def gen():
    for i  in range(1,5):
        yield i

gen()

生成器執行的時候,呼叫函式gen(),呼叫這個函式的時候,這個函式裡面的程式碼不會直接執行。

程式碼修改成這樣:

def gen():
    for i  in range(1,5):
        yield i

g = gen()
print(g)

只有通過next()方法往生成器裡面取值的時候,它才會從程式碼上面往下面執行。

這個send()方法可將資料傳到生成器裡面。使用next(),從生成器裡面獲取出一個值。如果使用send()方法,它也能夠獲取出來一條資料。

def gen():
    for i  in range(1,5):
        se = yield i
        print(se)

g = gen()
print(next(g))
print(g.send(100))

send()方法可以往生成器裡面傳入一個值。

通過send()方法生成資料的時候,它也可以往裡面傳送一個 100 的值。

5.資料傳送到生成器,在哪個地方呢?

如果通過next()去取值的話,這個yield完畢後是沒有返回內容的。

程式碼詳解:

第一輪: 迴圈進來,通過next()去取值生成了一個 1:

def gen():
    for i  in range(1,5):
        se = yield i
        print(se)

g = gen()
print(next(g))

第二輪: 通過print(g.send(100))去傳送值,然後列印:

def gen():
    for i  in range(1,5):
        se = yield i
        print('se的值:',se)

g = gen()
print(next(g))
print(g.send(100))

在第一輪結束之後,在yield這裡,yield完畢就停止了。在第一輪yield完之後,第二輪通過send()傳值進去,傳到se那裡,列印出來 100。

然後再往上返回一個資料,又暫停,返回第二條資料就是個 2。

第三輪: 通過next()再去生成一條元素,又觸發了yield i這個地方,這裡釋放了,往後面走,往後面走的話,但是沒有放資料進來,這個時候se是空的,列印出來的se是空的。

然後再往上,生成一條元素到 3,然後又停在yield i這個地方了,生成完元素,把這個值返回出去。

def gen():
    for i  in range(1,5):
        se = yield i
        print('se的值:',se)

g = gen()
print(next(g))
print(g.send(100))
print(next(g))

再次next()或者send()來觸發它的時候,它會這樣走:

注意: yield接收不是存在i中,這個yield返回出來的i是遍歷出來的內容。

send()發進去的,是yield i這裡執行完畢之後,當下一個send()觸發的時候,它把這個值傳送到yield i這裡執行完畢之後的一個結果。

yield i這裡把這個i返回出去,就停在這裡不動了。send()傳送個資料進去,那麼資料就傳送到個yield i這地方。

相當於yield i這個地方返回的一個結果,也就是send()發進去的內容,如果send()不發進去內容,返回出來是個空的。

溫馨提示:生成器<迭代器<可迭代物件

三、系列推薦


公眾號 「清菡軟體測試」 首發,更多原創文章:清菡軟體測試 109+原創文章,歡迎關注、交流,禁止第三方擅自轉載。

相關文章