Python3 基礎 -- 迭代器與生成器 !

約定:

「yield值」:指「生成器函數」返回的值,即,yield關鍵字後面的表達式的值

可迭代對象(iterable)

「可迭代對象」:簡單的概括就是,能從其獲取一個「迭代器對象」的對象。

所有的序列類型(例如:list,str和tuple)和一些非序列類型(例如:dict,file)對象,以及任何用戶定義的包含__iter__()函數或者__getitem__()函數的類的對象(一般為用作容器的對象)都是「可迭代對象」。

__iter__()函數:返回一個「迭代器對象」。

**Python學習交流群:1004391443,這裡是python學習者聚集地,有大牛答疑,有資源共享!小編也準備了一份python學習資料,有想學習python編程的,或是轉行,或是大學生,還有工作中想提升自己能力的,正在學習的小夥伴歡迎加入學習。**

如果沒有定義__iter__()函數,則看有沒有定義__getitem__()函數,如果有定義,則Python會創建(一般就是iter()函數創建)一個內置的默認「迭代器對象」,該「迭代器對象」會調用__getitem__()函數。

當一個「可迭代對象」作為參數傳給內置的iter()函數時,iter()函數將返回一個「迭代器對象」(它會調用剛剛談到的__iter__()以獲得一個「迭代器對象」,或是自己創建一個「迭代器對象」調用__getitem__())。通常我們沒有必要主動調用iter()函數,for循環語句自動調用iter()函數,在循環執行期間會暫存這個返回的「迭代器對象」。

迭代器對象(iterator)

首先了解一個協議,「迭代器協議」:

「迭代器協議」定義了兩個函數:

__iter__():返回「迭代器對象」自己。(實現時,如果你很確定該對象肯定不會作為「可迭代對象」使用,也可以不實現這個函數,但是很顯然,你不確定,所以還是實現這個函數吧。)

__next__():從容器中返回下一項,如果沒有下一項了,則拋出StopIteration異常。

「迭代器類型」:遵循「迭代器協議」的類型

「迭代器對象」:「迭代器類型」的實例對象。

反覆的調用「迭代器對象」的__next__()函數(將「迭代器對象」作為參數傳遞給內置的next()函數會調用「迭代器對象」的__next__()函數)將連續返回容器中的數據。當沒有更多的數據可用時,將會拋出StopIteration異常,此時「迭代器對象」已經耗盡了它的數據,以後再次調用它的__next__()都只會拋出StopIteration異常(可以概括為:對於這個「迭代器對象」,一次拋出StopIteration,終身拋出StopIteration)。

「迭代器對象」的__iter__()函數返回「迭代器對象」本身,這樣每個「迭代器對象」也是一個「可迭代對象」,可使用「可迭代對象」的任何地方都可以使用「迭代器對象」。

有一點需要注意:一個「可迭代對象」每次作為參數調用iter()函數或者用於for循環時,都將生成一個新的迭代器對象。

下面我們來實現一個自己的「可迭代對象」和「迭代器對象」:

#這是一個迭代器類型
class iterObj:
def __iter__(self):#實現這個函數,便可執行iter(iterObj對象)
return self
def __next__(self):#實現這個函數,便是迭代器類型
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
#這是一個可迭代類型
class Reverse:
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):#實現了這個函數,便是可迭代的
iter = iterObj()
iter.data = self.data
iter.index = len(self.data)
return iter
# def __getitem__(self, index):#如果沒有實現__iter__函數,但是實現了這個函數,則python創建的迭代器對象會調用該函數。
#如果想提前結束,可以拋出異常
# if index == 1:
# raise StopIteration
# return self.data[index]
rev = Reverse('spam')
for char in rev:
print(char)

關於iter()函數

通常使用iter()函數都只是傳遞一個參數,但其實它接受兩個參數:

iter(object[,sentinel]):如果只傳了第一個參數,那麼這個對象必須實現了__iter__()函數或者實現了__getitem__()函數,否則會拋出TypeError的異常。在我們剛剛編寫的例子就符合要求。

如果傳遞了第二個參數,那麼第一個參數必須是「可調用的對象」(例如函數),iter()函數會創建一個「迭代器對象」,這個「迭代器對象」的__next__()函數會調用第一個參數,當第一個參數返回的值等於第二個參數時,拋出StopIteration異常,否則返回該值。來看一個例子:

class Reverse:
def __init__(self, data):
self.data = data
self.index = len(data)
def callable(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
rev = Reverse('spam')
ite = iter(rev.callable, 'a')
print(ite)#<callable>
for char in ite:#打印m
print(char)
/<callable>

生成器對象(Generators)

「生成器對象」其實也是「迭代器對象」,因為從「生成器對象」中獲取「迭代器對象」時只是簡單的把自己返回了(類似我們前面自己編寫的例子),但是它特殊就特殊在yield語句上。

當一個函數中含有yield語句時,那麼稱這個函數為「生成器函數」。

當我們定義了一個「生成器函數」時,__iter__()和__next__()會被自動定義(不論是通過一個內置的「生成器類」還是其他辦法,總之,這兩個函數被自動定義了),當調用這個「生成器函數」時,會返回一個「生成器對象」。

上一節我們自己編寫了一個「迭代器」例子,相比較後我們可以猜到,其實Python為「生成器」自動創建的那兩個函數與我們自己寫的在目的上沒什麼區別,無非都是為了實現「迭代器協議」,從這個角度來說,前面我們自己寫的Reverse類的實例,也可以稱為「生成器對象」,只是在具體實現上有區別,特別是__next__()函數,至於「生成器對象」的其他特性,我們照樣可以自己加上。

「生成器對象」的__next__()函數會啟動「生成器函數」的執行或者從上一次執行yield語句的地方恢復執行,直到遇到下一個yield語句後,掛起(或者稱為暫停),同時將yield關鍵字後面的表達式的值返回給調用者(對於掛起,我們只需要知道Python會把當時的執行狀態保存下來以便下次恢復)。來看一個例子:

def reverse(data):
print('reverse is called')#3
for index in range(len(data)-1, -1, -1):#4 #10
yield data[index]#5 #8 #11
print('執行yield表達式以後的語句')#9
#這裡並沒有輸出'reverse is called',僅僅是創建了一個生成器對象,並沒有執行reverse函數。
re = reverse('golf')#1
#for語句會調用iter(re),接著會調用返回對象的__next__()函數
for char in re:#2 #7
print(char)#6
#輸出:f l o g

執行過程是這樣的:

#1 創建一個「生成器對象」。

#2 for語句會調用iter(re),返回對象其實就是re本身(其實這裡也相當於創建了一個迭代器),接著會調用返回對象的__next__()函數,__next__()函數啟動執行「生成器函數」。

#3 打印

#4 執行for循環的第一次循環

#5 遇到「yield表達式」掛起,返回「yield值」(這裡就是:data[index]),執行流回到調用者

#6 此時char已經被賦值,就是#5中「yield表達式」的值,打印

#7 繼續執行for循環,再次調用__next__()

#8 從步驟5掛起的地方恢復執行

#9 執行打印:執行「yield表達式」以後的語句

#10 繼續執行for循環

#11 再次遇到「yield表達式」回到步驟#5

直到這個由「生成器對象」創建的「迭代器對象」耗盡,執行結束。

yield表達式

到這裡是不是覺得對yield有點感覺了?請先在新裡面默唸三遍:「yield 100」是一個表達式。

我們接著來看,下面例子中m的值分別為多少?

def yield_func():
print('— first next() is called---')
m = yield 100 #1
print('— second next() is called—')
print(m)
m = yield 100 #2
print('— send(value) is called—')
print(m)
yld = yield_func()
next(yld)#輸出— first next() is called—
next(yld)#輸出—-second next() is called—- 和 None
yld.send(1)#輸出—-send(value) is called—-和 1 並且拋出異常:StopIteration

上面m的值依次打印為None和1:

當第一個next()執行的時候,僅僅打印一行文字;

然後執行第二個next()函數,此時從#1處恢復,恢復時yield 100表達式的返回值賦給了m,那這個表達式的值是多少?從後面的打印可以看到是None;

然後是執行yld.send(1),此時從#2處恢復,恢復時仍然是把yield 100表達式的值賦給了m,這次打印出來m的值為:1,恰好是我們傳進去的值。

結論:

「生成器對象」的send(value)函數會恢復執行「生成器函數」,同時把參數作為當前恢復的「yield表達式」的返回值,而next()(其內部調用了「生成器對象」的__next__()函數)相等於send(None),即,當通過next()恢復時,當前恢復的「yield表達式」的值為None。

至於最後的那個異常,這是常規操作,當一個迭代器迭代完成後,就會拋出這個異常,上一節我們自己實現的那個迭代器,也是拋出這個異常,之前這個異常為什麼沒有中斷程序是因為內置的Python實現幫我們捕獲了這個異常。

需要注意一點:由於最開始時並沒有「yield表達式」接收這個傳進來的值,所以當啟動「生成器函數」時,需要傳空值,即send(None)或者使用next()

「生成器對象」(除了__next__()和send(value))還有其他兩個函數:

generator.throw(type[,value[,traceback]]):從「生成器函數」掛起的地方拋出一個type類型的異常,並返回下一個「yield值」,如果「生成器函數」沒有返回「yield值」就退出了,那麼會拋出StopIteration異常。

generator.close():從「生成器函數」掛起的地方拋出一個GeneratorExit異常。如果此 後「生成器函數」正常執行結束則關閉(此時拋出的GeneratorExit異常被捕獲了),如果繼續返回「yield值」則會拋出RuntimeError。

最後來看一個綜合的例子:

def echo(value=None):
print("Execution starts when 'next()' is called for the first time.")
try:
while True:
try:
value = (yield value)
except Exception as e:
value = e
#break #這裡如果直接beak退出循環,則「生成器函數」執行結束退出,調用generator.throw(TypeError, "spam”)時,會拋出StopIteration異常
#except: #這裡如果捕獲所有的異常,則while循環會繼續執行,此時會close()拋出RuntimeError
# pass
finally:
print("Don't forget to clean up when 'close()' is called.")
generator = echo(1)
print(next(generator))#輸出:Execution starts when 'next()' is called for the first time.和 1
print(next(generator))#輸出:None
print(generator.send(2))#輸出:2
generator.throw(TypeError, "spam")#輸出:TypeError('spam',)
generator.close()#輸出:Don't forget to clean up when 'close()' is called.

這裡提醒一下:例子中如果拋出的異常被捕獲,僅僅執行賦值,然後while循環會繼續執行。


分享到:


相關文章: