淺談Python中的可迭代對象、迭代器、For循環(huán)工作機(jī)制、生成器
1.iterable iterator區(qū)別
要了解兩者區(qū)別,先要了解一下迭代器協(xié)議:
迭代器協(xié)議是指:對象需要提供__next__()方法,它返回迭代中的元素,在沒有更多元素后,拋出StopIteration異常,終止迭代。
可迭代對象就是:實(shí)現(xiàn)了迭代器協(xié)議的對象。
協(xié)議是一種約定,可迭代對象實(shí)現(xiàn)迭代器協(xié)議,Python的內(nèi)置工具(如for循環(huán),sum,min,max函數(shù)等)通過迭代器協(xié)議訪問對象,因此,for循環(huán)并不需要知道對象具體是什么,只需要知道對象能夠?qū)崿F(xiàn)迭代器協(xié)議即可。
迭代器(iterator)與可迭代對象(iterable)并不是同一個概念。
直觀上:
1.可迭代對象(iterable):凡是具有__iter__的方法的類,都是可迭代的類??傻悇?chuàng)建的對象實(shí)現(xiàn)了__iter__方法,因此就是可迭代對象。用list、tuple等容器創(chuàng)建的對象,都是可迭代對象??傻鷮ο笸ㄟ^__iter__方法返回一個迭代器,然后在內(nèi)部調(diào)用__next__方法進(jìn)行迭代,最后沒有元素時,拋出異常(這個異常python自己會處理,不會讓開發(fā)者看見)。
2.迭代器(iterator):迭代器對象必須同時實(shí)現(xiàn)__iter__和__next__方法才是迭代器。對于迭代器來說,__iter__ 返回的是它自身 self,__next__ 則是返回迭代器中的下一個值,最后沒有元素時,拋出異常(異??梢员婚_發(fā)者看到)。
從上面2點(diǎn)可以看出:
1.迭代器一定是可迭代對象,因為它實(shí)現(xiàn)了__iter__()方法;
2.通過iter()方法(在類的內(nèi)部就是__iter__)能夠使一個可迭代對象返回一個迭代器。
3.迭代器的 __iter__ 方法返回的是自身,并不產(chǎn)生新的迭代器對象。而可迭代對象的 __iter__ 方法通常會返回一個新的迭代器對象。
第3點(diǎn)性質(zhì)正是可迭代對象可以重復(fù)遍歷的原因(每次返回一個獨(dú)立的迭代器,就可以保證不同的迭代過程不會互相影響);而迭代器由于返回自身,因此只能遍歷一次。
上面3點(diǎn)可以通過下面的例子看出來:
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 ##id可以查看一個對象在內(nèi)存中的地址 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:可迭代對象每次調(diào)用iter方法都會返回一個新的迭代器對象,而迭代器對象調(diào)用iter方法返回自身 67162576 67162576 67162688 67162632 67162856 67163024
2.iterable的工作機(jī)制
拿一個例子看看,首先定義一個有__iter__方法,但是沒有next()方法的類 (PS:在python2中是next(),python3是__next__()):
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 ##可重復(fù)遍歷 for i in test: print i ##result 80 90 95
上面代碼的結(jié)果印證了定義中提到的:
缺少了next()方法,可迭代對象就不是迭代器。
此外,注意到:可迭代對象通過__iter__方法每次都返回了一個獨(dú)立的迭代器,這樣就可以保證不同的迭代過程不會互相影響。
也就是說,通過iterable可以實(shí)現(xiàn)重復(fù)遍歷,而迭代器是無法重復(fù)遍歷的!
因此,如果想要把可迭代對象轉(zhuǎn)變?yōu)榈?,可以先調(diào)用iter()方法返回一個迭代器。然后就可以用next()不斷迭代了!
print isinstance(iter(test),Iterator) testIter=iter(test) print testIter.next() print testIter.next() print testIter.next() ##result True 80 90 95 ##一旦取完了可迭代對象中所有的元素,再次調(diào)用next就會發(fā)生異常 print testIter.next() ##result StopIteration:
3.迭代器Iterator的工作機(jī)制
看下面這個例子:
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 ##如果此時再對test這個迭代器調(diào)用next方法,就會拋出異常 test.next() ##result StopIteration:
這個例子印證了定義中的:迭代器對象必須同時實(shí)現(xiàn)__iter__和__next__方法才是迭代器。
那么,使用迭代器好處在哪呢?
Python的Iterator對象表示的是一個數(shù)據(jù)流,Iterator對象可以被next()函數(shù)調(diào)用并不斷返回下一個數(shù)據(jù),直到?jīng)]有數(shù)據(jù)時拋出StopIteration錯誤??梢园堰@個數(shù)據(jù)流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函數(shù)實(shí)現(xiàn)按需計算下一個數(shù)據(jù),所以Iterator的計算是惰性的,只有在需要返回下一個數(shù)據(jù)時它才會計算。
一個很常見的應(yīng)用就是:Python在處理列表的時候,是直接把整個列表讀進(jìn)內(nèi)存的,當(dāng)遇到大量樣本時的時候會變得很慢。而迭代器的優(yōu)勢在于只把需要的元素讀進(jìn)內(nèi)存,因此占用內(nèi)存更少。
換句話說,迭代器是一種惰性求值模式,它是有狀態(tài)的,只有在調(diào)用時才返回值,沒有調(diào)用的時候就等待下一次調(diào)用。這樣就節(jié)省了大量內(nèi)存空間。
這個例子印證了定義中的:迭代器對象必須同時實(shí)現(xiàn)__iter__和__next__方法才是迭代器。
那么,使用迭代器好處在哪呢?
Python的Iterator對象表示的是一個數(shù)據(jù)流,Iterator對象可以被next()函數(shù)調(diào)用并不斷返回下一個數(shù)據(jù),直到?jīng)]有數(shù)據(jù)時拋出StopIteration錯誤??梢园堰@個數(shù)據(jù)流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函數(shù)實(shí)現(xiàn)按需計算下一個數(shù)據(jù),所以Iterator的計算是惰性的,只有在需要返回下一個數(shù)據(jù)時它才會計算。
一個很常見的應(yīng)用就是:Python在處理列表的時候,是直接把整個列表讀進(jìn)內(nèi)存的,當(dāng)遇到大量樣本時的時候會變得很慢。而迭代器的優(yōu)勢在于只把需要的元素讀進(jìn)內(nèi)存,因此占用內(nèi)存更少。
換句話說,迭代器是一種惰性求值模式,它是有狀態(tài)的,只有在調(diào)用時才返回值,沒有調(diào)用的時候就等待下一次調(diào)用。這樣就節(jié)省了大量內(nèi)存空間。
4.for循環(huán)的工作機(jī)制
有了上面2個例子,就可以總結(jié)一下在可迭代對象與迭代器中的For循環(huán)工作機(jī)制了。
當(dāng)對象本身就是迭代器時,F(xiàn)or循環(huán)工作機(jī)制:
- 調(diào)用 __iter__方法,返回自身self,也就是返回迭代器。
- 不斷地調(diào)用迭代器的next()方法,每次按序返回迭代器中的一個值。
- 迭代到最后沒有元素時,就拋出異常 StopIteration
在可迭代對象中,for循環(huán)工作機(jī)制:
- 先判斷對象是否為可迭代對象(等價于判斷有沒有__iter__或__getitem__方法),沒有的話直接報錯,拋出TypeError異常。有的話,調(diào)用 __iter__方法,返回一個迭代器。
- 在python內(nèi)部不斷地調(diào)用迭代器的__next__方法,每次按序返回迭代器中的一個值。
- 迭代到最后沒有元素時,就拋出異常 StopIteration,這個異常 python 自己會處理,不會暴露給開發(fā)者。
借用網(wǎng)絡(luò)上的一張圖直觀理解一下:
此外,還要注意,python中的for循環(huán)其實(shí)兼容了兩種機(jī)制:
- 如果對象有__iter__會返回一個迭代器。
- 如果對象沒有__iter__,但是實(shí)現(xiàn)了__getitem__,會改用下標(biāo)迭代的方式。
- __getitem__可以幫助一個對象進(jìn)行取數(shù)和切片操作。
當(dāng)for發(fā)現(xiàn)沒有__iter__但是有__getitem__的時候,會從0開始依次讀取相應(yīng)的下標(biāo),直到發(fā)生IndexError為止,這是一種舊的迭代協(xié)議。iter方法也會處理這種情況,在不存在__iter__的時候,返回一個下標(biāo)迭代的iterator對象來代替。一個重要的例子是str,字符串就是沒有__iter__方法的,但是卻依然可以迭代,原因就是其在for循環(huán)時調(diào)用了__getitem__方法。
看一個例子:
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 for i in range(0,3): print test[i] ##result 80 90 95 for i in iter(test): print i ##result 80 90 95
可以看到,實(shí)現(xiàn)了__getitem__方法的對象本身,盡管不是iterable與iterator,仍舊是可以調(diào)用for循環(huán)的。
通過iter方法,返回一個下標(biāo)迭代的iterator對象。
5.generator的原理
最后說一下生成器,生成器是一種特殊的迭代器,當(dāng)然也是可迭代對象。
對于生成器,Python會自動實(shí)現(xiàn)迭代器協(xié)議,以便應(yīng)用到迭代中(如for循環(huán),sum函數(shù))。由于生成器自動實(shí)現(xiàn)了迭代器協(xié)議,所以,我們可以調(diào)用它的next方法,并且,在沒有值可以返回的時候,生成器自動產(chǎn)生StopIteration異常。
創(chuàng)建生成器的方法:將return 改為yield。具體的實(shí)現(xiàn)網(wǎng)絡(luò)上教程很多,不細(xì)說了。
6.總結(jié)
到一幅圖片很好的描述了本文的所有內(nèi)容,就拿它作為文末的總結(jié)吧!
以上所述是小編給大家介紹的Python中的可迭代對象、迭代器、For循環(huán)工作機(jī)制、生成器詳解整合,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
PyTorch并行訓(xùn)練DistributedDataParallel完整demo
這篇文章主要為大家介紹了PyTorch并行訓(xùn)練DistributedDataParallel完整demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06詳解Python中__new__和__init__的區(qū)別與聯(lián)系
在Python中,每個對象都有兩個特殊的方法:__new__和__init__,本文將詳細(xì)介紹這兩個方法的不同之處以及它們之間的聯(lián)系,具有一定的參考價值,感興趣的可以了解一下2023-12-12python使用matplotlib畫柱狀圖、散點(diǎn)圖
這篇文章主要為大家詳細(xì)介紹了python使用matplotlib畫柱狀圖、散點(diǎn)圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-03-03Python內(nèi)置函數(shù)的用法實(shí)例教程
這篇文章主要介紹了Python內(nèi)置函數(shù)的用法,包括求絕對值的abs()函數(shù)及數(shù)值類型轉(zhuǎn)換函數(shù)等,需要的朋友可以參考下2014-09-09完美解決Django2.0中models下的ForeignKey()問題
這篇文章主要介紹了完美解決Django2.0中models下的ForeignKey()問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-05-05