Python中的可迭代物件、迭代器、For迴圈工作機制、生成器

1.iterable iterator區別
要了解兩者區別,先要了解一下迭代器協議:
迭代器協議是指:物件需要提供 next ()方法,它返回迭代中的元素,在沒有更多元素後,丟擲StopIteration異常,終止迭代。
可迭代物件就是:實現了迭代器協議的物件。
協議是一種約定,可迭代物件實現迭代器協議,Python的內建工具(如for迴圈,sum,min,max函式等)通過迭代器協議訪問物件,因此,for迴圈並不需要知道物件具體是什麼,只需要知道物件能夠實現迭代器協議即可。
迭代器(iterator)與可迭代物件(iterable)並不是同一個概念。
直觀上:
1.可迭代物件(iterable):凡是具有 iter 的方法的類,都是可迭代的類。可迭代類建立的物件實現了 iter 方法,因此就是可迭代物件。用list、tuple等容器建立的物件,都是可迭代物件。 可迭代物件通過 iter 方法返回一個迭代器 ,然後在內部呼叫 next 方法進行迭代,最後沒有元素時,丟擲異常(這個異常python自己會處理,不會讓開發者看見)。
2.迭代器(iterator):迭代器物件必須同時實現 iter 和 next 方法才是迭代器。 對於迭代器來說, iter 返回的是它自身 self , next 則是返回迭代器中的下一個值,最後沒有元素時,丟擲異常(異常可以被開發者看到)。
從上面2點可以看出:
1.迭代器一定是可迭代物件,因為它實現了 iter ()方法;
2.通過iter()方法(在類的內部就是 iter )能夠使一個可迭代物件返回一個迭代器。
3.迭代器的iter方法返回的是自身,並不產生新的迭代器物件。而可迭代物件的iter方法通常會返回一個新的迭代器物件。
第3點性質正是可迭代物件可以重複遍歷的原因(每次返回一個獨立的迭代器,就可以保證不同的迭代過程不會互相影響);而迭代器由於返回自身,因此只能遍歷一次。
身為老司機,還是得分享些乾貨精品學習資料的,推薦下小編建立的Python學習交流群556370268,送給每一位小夥伴,這裡是小白聚集地,每天還會直播和大家交流分享經驗哦,歡迎初學和進階中的小夥伴。
上面3點可以通過下面的例子看出來:
<pre style="margin: 0px; padding: 0px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 18px; line-height: inherit; font-family: inherit; vertical-align: baseline; word-break: break-word; color: rgb(93, 93, 93); letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> from collections import Iterable from collections import Iterator print isinstance(iter([1,2]),Iterator) print isinstance(iter([1,2]),Iterable) print isinstance([1,2],Iterator) print isinstance([1,2],Iterable) ##result True True False True </pre> <pre style="margin: 0px; padding: 0px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 18px; line-height: inherit; font-family: inherit; vertical-align: baseline; word-break: break-word; color: rgb(93, 93, 93); letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> ##id可以檢視一個物件在記憶體中的地址 test=[1,2,3] testIter=iter(test) print id(testIter) print id(testIter) print id(iter(test)) print id(iter(test)) print id(test.__iter__()) print id(test.__iter__()) ##result:可迭代物件每次呼叫iter方法都會返回一個新的迭代器物件,而迭代器物件呼叫iter方法返回自身 67162576 67162576 67162688 67162632 67162856 67163024 </pre>
2.iterable的工作機制
拿一個例子看看,首先定義一個有 iter 方法,但是沒有next()方法的類 (PS:在python2中是next(),python3是 next ()):
<pre style="margin: 0px; padding: 0px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 18px; line-height: inherit; font-family: inherit; vertical-align: baseline; word-break: break-word; color: rgb(93, 93, 93); letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> from collections import Iterable, Iterator class Student(object): def __init__(self,score): self.score=score def __iter__(self): return iter(self.score) test= Student([80,90,95]) print isinstance(test, Iterable) print isinstance(test, Iterator) for i in test: print i ##result True False 80 90 95 </pre> <pre style="margin: 0px; padding: 0px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 18px; line-height: inherit; font-family: inherit; vertical-align: baseline; word-break: break-word; color: rgb(93, 93, 93); letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> ##可重複遍歷 for i in test: print i ##result 80 90 95 </pre>
上面程式碼的結果印證了定義中提到的:
缺少了next()方法,可迭代物件就不是迭代器。
此外,注意到: 可迭代物件通過 iter 方法每次都返回了一個獨立的迭代器,這樣就可以保證不同的迭代過程不會互相影響。
也就是說,通過iterable可以實現重複遍歷,而迭代器是無法重複遍歷的!
因此,如果想要把可迭代物件轉變為迭代器,可以先呼叫iter()方法返回一個迭代器。然後就可以用next()不斷迭代了!
<pre style="margin: 0px; padding: 0px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 18px; line-height: inherit; font-family: inherit; vertical-align: baseline; word-break: break-word; color: rgb(93, 93, 93); letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> print isinstance(iter(test),Iterator) testIter=iter(test) print testIter.next() print testIter.next() print testIter.next() ##result True 80 90 95 ##一旦取完了可迭代物件中所有的元素,再次呼叫next就會發生異常 print testIter.next() ##result StopIteration: </pre>
3.迭代器Iterator的工作機制
看下面這個例子:
<pre style="margin: 0px; padding: 0px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 18px; line-height: inherit; font-family: inherit; vertical-align: baseline; word-break: break-word; color: rgb(93, 93, 93); letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> class Student(object): def __init__(self,score): self.score=score def __iter__(self): return self def next(self): if self.score<100: self.score+=1 return self.score else: raise StopIteration() test= Student(90) print isinstance(test, Iterable) print isinstance(test, Iterator) print test.next() print test.next() print test.next() for i in test: print i ##result True True 91 92 93 94 95 96 97 98 99 100 </pre> <pre style="margin: 0px; padding: 0px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 18px; line-height: inherit; font-family: inherit; vertical-align: baseline; word-break: break-word; color: rgb(93, 93, 93); letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> ##如果此時再對test這個迭代器呼叫next方法,就會丟擲異常 test.next() ##result StopIteration: </pre>
這個例子印證了定義中的:迭代器物件必須同時實現 iter 和 next 方法才是迭代器。
那麼,使用迭代器好處在哪呢?
Python的Iterator物件表示的是一個數據流,Iterator物件可以被next()函式呼叫並不斷返回下一個資料,直到沒有資料時丟擲StopIteration錯誤。可以把這個資料流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函式實現按需計算下一個資料,所以Iterator的計算是惰性的,只有在需要返回下一個資料時它才會計算。
一個很常見的應用就是:Python在處理列表的時候,是直接把整個列表讀進記憶體的,當遇到大量樣本時的時候會變得很慢。而迭代器的優勢在於只把需要的元素讀進記憶體,因此佔用記憶體更少。
換句話說,迭代器是一種惰性求值模式,它是有狀態的,只有在呼叫時才返回值,沒有呼叫的時候就等待下一次呼叫。這樣就節省了大量記憶體空間。
4.for迴圈的工作機制
有了上面2個例子,就可以總結一下在可迭代物件與迭代器中的For迴圈工作機制了。
當物件本身就是迭代器時,For迴圈工作機制:
1.呼叫 iter 方法,返回自身self,也就是返回迭代器。
2.不斷地呼叫迭代器的next()方法,每次按序返回迭代器中的一個值。
3.迭代到最後沒有元素時,就丟擲異常 StopIteration。
在可迭代物件中,for迴圈工作機制:
1.先判斷物件是否為可迭代物件(等價於判斷有沒有 iter 或 getitem 方法),沒有的話直接報錯,丟擲TypeError異常。有的話,呼叫 iter 方法,返回一個迭代器。
2.在python內部不斷地呼叫迭代器的 next 方法,每次按序返回迭代器中的一個值。
3.迭代到最後沒有元素時,就丟擲異常 StopIteration,這個異常 python 自己會處理,不會暴露給開發者。
借用網路上的一張圖直觀理解一下:

此外,還要注意, python中的for迴圈其實相容了兩種機制:
1.如果物件有 iter 會返回一個迭代器。
2.如果物件沒有 iter ,但是實現了 getitem ,會改用下標迭代的方式。
getitem可以幫助一個物件進行取數和切片操作。
當for發現沒有 iter 但是有 getitem 的時候,會從0開始依次讀取相應的下標,直到發生IndexError為止,這是一種舊的迭代協議。iter方法也會處理這種情況,在不存在 iter 的時候,返回一個下標迭代的iterator物件來代替。 一個重要的例子是str,字串就是沒有 iter 方法的,但是卻依然可以迭代,原因就是其在for迴圈時呼叫了 getitem 方法。
看一個例子:
<pre style="margin: 0px; padding: 0px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 18px; line-height: inherit; font-family: inherit; vertical-align: baseline; word-break: break-word; color: rgb(93, 93, 93); letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> from collections import Iterable, Iterator class Student(object): def __init__(self,score): self.score=score def __getitem__(self,n): return self.score[n] test= Student([80,90,95]) print isinstance(test, Iterable) print isinstance(test, Iterator) print isinstance(iter(test), Iterable) print isinstance(iter(test), Iterator) for i in test: print i ##result False False True True 80 90 95 </pre> <pre style="margin: 0px; padding: 0px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 18px; line-height: inherit; font-family: inherit; vertical-align: baseline; word-break: break-word; color: rgb(93, 93, 93); letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> for i in range(0,3): print test[i] ##result 80 90 95 </pre> <pre style="margin: 0px; padding: 0px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 18px; line-height: inherit; font-family: inherit; vertical-align: baseline; word-break: break-word; color: rgb(93, 93, 93); letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> for i in iter(test): print i ##result 80 90 95 </pre>
可以看到,實現了 getitem 方法的物件本身,儘管不是iterable與iterator,仍舊是可以呼叫for迴圈的。
通過iter方法,返回一個下標迭代的iterator物件。
5.generator的原理
最後說一下生成器,生成器是一種特殊的迭代器,當然也是可迭代物件。
對於生成器,Python會自動實現迭代器協議,以便應用到迭代中(如for迴圈,sum函式)。由於生成器自動實現了迭代器協議,所以,我們可以呼叫它的next方法,並且,在沒有值可以返回的時候,生成器自動產生StopIteration異常。
建立生成器的方法:將return 改為yield。具體的實現網路上教程很多,不細說了。
6.總結
到一幅圖片很好的描述了本文的所有內容,就拿它作為文末的總結吧!
