區別迭代器和生成器
一:迭代器 定義: 迭代器就是實現了迭代器協議的物件。那麼小夥伴們就要問了,什麼是迭代器協議呢?
設計的知識點: 迭代器協議:即物件實現了__iter__()和__next__()兩個方法
__iter__()方法:返回迭代器物件本身
__next__()方法:返回容器的下一個元素,在結尾時引發一個StopIteration異常終止迭代器
那麼迭代器究竟該如何建立呢,建立如下:
迭代器的建立:使用iter()方法返回一個迭代器物件,iter()的引數為可迭代物件。那麼這裡有小夥伴又要問了,什麼是可迭代物件??
可迭代物件:即實現了__iter__()方法的物件,可以使用該方法返回一個迭代器物件。
工作過程: 一般迭代器工作過程 : 現在我們做一個例子來更加形象的講解迭代器的工作流程,這裡我將用List物件來進行例項。
首先我們先定義一個List物件,呼叫__iter__()方法,可以發現List物件確實是一個可迭代物件(即實現了__iter__()方法)。然後我們呼叫__next__()方法,可以發現系統提示並沒有__next__()這個屬性,因此表示List物件不是一個迭代器物件。
>>> list=[1,2,3,4,5] >>> list.__iter__() <list_iterator object at 0x0000006EFA076080> >>> list.__next__() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'list' object has no attribute '__next__' 之後,我們將使用List物件建立一個迭代器物件,然後讓迭代器物件呼叫__next__()方法,一次次返回可迭代物件(即List物件)的元素,直到最後丟擲一個StopIteration異常。---注意,產生一個異常並不等於出現錯誤,而是告訴外部呼叫者迭代完成了,外部的呼叫者嘗試去捕獲這個異常去做進一步的處理。
>>> li=list.__iter__() >>> li.__next__() 1 >>> li.__next__() 2 >>> li.__next__() 3 >>> li.__next__() 4 >>> li.__next__() 5 >>> li.__next__() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration 這就是迭代器的整個工作過程!!
迭代器迭代可變物件: 當迭代器迭代可變物件時,一個序列的迭代器只是記錄當前到達了序列中的第幾個元素,所以如果在迭代過程中改變了序列的元素。更新會立即反應到所迭代的條目上。
for迴圈迭代器工作過程 : 首先,我們同樣使用List物件作為例子。定義一個List物件後,使用for迴圈遍歷List物件。
>>> list=[1,2,3,4,5] >>> for x in list: ... print(list[x-1]) ... 1 2 3 4 5 那麼,究竟for迴圈是怎樣遍歷/迭代可迭代物件的呢?首先,List物件先使用__iter__()方法返回一個迭代器,之後使用這個跌打器一次次呼叫__next__()方法,一個接著一個返回List物件的元素,並將返回的元素賦值給變數x。每一次的賦值都會執行一次for迴圈體中的內容。直到最後List物件中沒有元素,就丟擲一個StopIteration異常給外部呼叫者處理。
可以參考下面的程式碼:
>>> list=[1,2,3,4,5] >>> li=list.__iter__() >>> x=li.__next__() >>> print(list[x-1]) 1 >>> x=li.__next__() >>> print(list[x-1]) 2 >>> x=li.__next__() >>> print(list[x-1]) 3 >>> x=li.__next__() >>> print(list[x-1]) 4 >>> x=li.__next__() >>> print(list[x-1]) 5 >>> x=li.__next__() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration 注:for迴圈本質上也是一種迭代器。
原理: 迭代器工作原理:
for迴圈工作原理:
二:生成器 定義: 生成器是一個包含有yield的函式,同時也是一個迭代器。那麼,小夥伴們可能要問yield是什麼呢?哈哈,yield就是生成器的重點!!我會在接下來慢慢進行講解。
設計的知識點: 生成器實際上是特殊的一種迭代器,即完全可以像使用迭代器一樣使用生成器。說了這麼多,究竟該怎樣建立生成器呢?
生成器的建立:
1、使用生成器函式的返回值,返回一個生成器物件
2、使用生成器表示式的返回值,返回一個生成器物件(生成器表示式在後面講解)
>>> def fun(x): ... print("前") ... yield x ... print("後") ... >>> ge=fun(1) >>> ge <generator object fun at 0x000000DB1AF14A40> >>> ge1=(x for y in [1,2,3,4,5]) >>> ge1 <generator object <genexpr> at 0x000000DB1AF149E8> 從上面的程式碼我們可以看出,變數ge和ge1都是generator object(生成器物件)。因此,兩種建立方式都可以返回一個生成器物件。
生成器表示式:類似於列表,只是將[]換成了(),生成器表示式返回一個生成器物件。
>>> list=[1,2,3,4,5] >>> ge1=[y for y in list ] >>> ge1 [1, 2, 3, 4, 5] >>> type(ge1) <class 'list'> >>> ge1=(x for x in list) >>> ge1 <generator object <genexpr> at 0x000000DB1AF14AF0> 由以上程式碼可以知道,兩種建立的不是一種物件。
工作過程: 接下來,我們同樣用一個例子來具體解釋生成器函式的工作過程。
首先,我們先利用生成器函式建立一個生成器f,之後我們不斷呼叫__next__()方法,直到最後丟擲一個異常。我們再來觀察程式碼的執行情況。
>>> def fun(x): ... z=0 ... print("前") ... yield x ... print("中") ... yield 2 ... y=yield z ... print(y) ... print("後") ... >>> f=fun(1) >>> next(f) 前 1 >>> next(f) 中 2 >>> next(f) 0 >>> next(f) None 後 Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration 首先我們先來看這段程式碼是怎樣執行的:我們先定義了一個生成器函式(內部包含yield),然後我們使用該函式建立一個生成器物件 f。
可以看出,當我們呼叫該函式時,該函式並沒有執行(因為如果執行了函式,會輸出一些結果)。當我們第一次呼叫next()方法的時候,發現程式居然執行了!!!為什麼會這樣呢?這就要從next()方法開始說起。
next()作用:第一次呼叫next()方法會啟動生成器函式,並執行到最靠近的yield語句的位置。
ok,明白了next()方法的作用之後,我們就明白了為什麼第一次的執行結果是:前。但是為什麼還有一個 1 呢?這就要來看看yield的作用了!
yield:在每一次執行next()方法之後,會將程式碼的執行許可權交給生成器函式去執行,一直執行到yield語句(yield語句也會執行)。yield語句會返回一個值,並儲存生成器中前面一次執行的狀態,並將程式碼的執行許可權交還給呼叫生成器的一方去執行呼叫方的程式碼。
當我們完全明白了next()和yield的作用之後,就能理解上面例子的執行流程了。第二次呼叫next()方法時,會從上一次執行的程式碼行之後執行(即從語句: print("中") 開始執行),一直執行到yield語句,返回一個值:2。第三次呼叫netx()方法時,同理,會輸出如上的結果。一次次呼叫next()方法,一直執行到最後沒有了yield語句,就會丟擲一個異常。
其實生成器除了next()方法外,還有send()、close()、throw()方法。
>>> def fun(x): ... z=0 ... print("qian") ... yield 1 ... print("zhong") ... yield x ... y=yield z ... print(y) ... print("hou") ... >>> f=fun(2) >>> next(f) qian 1 >>> next(f) zhong 2 >>> next(f) 0 >>> f.send(9) 9 hou Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration send()作用:在yield z執行後,執行send()方法後悔傳遞一個值給變數y。若在yield z執行之前執行send()方法,則不會傳遞值給變數y。
>>> def fun(x): ... z=0 ... print("qian") ... yield 1 ... print("zhong") ... yield x ... y=yield z ... print(y) ... print("hou") ... >>> f=fun(2) >>> next(f) qian 1 >>> f.close() >>> next(f) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration close():當呼叫該方法後,就會退出生成器函式,後面的呼叫會直接返回StopIteration異常。 >>> def gen(): ... while True: ... try: ... yield 'normal value' ... yield 'normal value 2' ... print('here') ... except ValueError: ... print('we got ValueError here') ... except TypeError: ... break ... >>> g=gen() >>> print(next(g)) normal value >>> print(g.throw(ValueError)) we got ValueError here normal value >>> print(next(g)) normal value 2 >>> print(g.throw(TypeError)) throw():呼叫該方法後,會忽略try語句中的程式碼,跳到throw的異常處執行,並執行下一個yield語句後停止;或者在沒有下一個yield的時候直接進行到程式的結尾。
在一個生成器中,如果沒有return,則預設執行到函式完畢時返回StopIteration;
如果遇到return,如果在執行過程中 return,則直接丟擲 StopIteration 終止迭代;
如果在return後返回一個值,那麼這個值為StopIteration異常的說明,不是程式的返回值。
區別return和yield:
return一旦使用,其(函式)作用域的變數等狀態會消失,並且會將程式碼的執行許可權交給外部的呼叫者;如果外部呼叫者不再呼叫該函式,就不會再將執行許可權交還給函式;若return後再次呼叫該函式,原函式的作用域等狀態會消失,生成的是一個新的函式的作用域(內部的各種狀態也是新的)。
yield會在外部呼叫者呼叫next()/send(None)方法時,許可權跳轉到生成器中,執行生成器的程式碼直到yield語句,yield返回一個值給外部呼叫者,並且將程式碼執行許可權也交給外部呼叫者,此時生成器中的狀態等會儲存,直到下一次外部呼叫者呼叫next()方法,會將程式碼的執行許可權還給生成器,並從上一次的狀態位置開始執行。
原理: 生成器表示式 生成器函式
三:兩者的區別 區別: 迭代器(多迭代):可以多次迭代
>>> myiterator = [x*x for x in range(3)] >>> for x in myiterator: ... print(x) ... 0 1 4 >>> for x in myiterator: ... print(x) ... 0 1 4 生成器(單迭代):只能迭代一次
>>> mygenerator = (x*x for x in range(3)) >>> for x in mygenerator: ... print(x) ... 0 1 4 >>> for x in mygeneration: ... print(x) ... Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'mygeneration' is not defined
--------------------- 來源:CSDN 原文:https://blog.csdn.net/qq_35187510/article/details/79594885