1 引言
只要你學(xué)了Python語言,就不會(huì)不知道for循環(huán),也肯定用for循環(huán)來遍歷一個(gè)列表(list),那為什么for循環(huán)可以遍歷list,而不能遍歷int類型對(duì)象呢?怎么讓一個(gè)自定義的對(duì)象可遍歷?
這篇博客中,我們來一起探索一下這個(gè)問題,在這個(gè)過程中,我們會(huì)介紹到迭代器、可迭代對(duì)象、生成器,更進(jìn)一步的,我們會(huì)詳細(xì)介紹他們的原理、異同。
2 迭代器與可迭代對(duì)象
在開始下面內(nèi)容之前,我們先說說標(biāo)題中的“迭代”一詞。什么是迭代?我認(rèn)為,迭代一個(gè)完整過程中的一個(gè)重復(fù),或者說每一次對(duì)過程的重復(fù)稱為一次“迭代”,而每一次迭代得到的結(jié)果會(huì)作為下一次迭代的初始值,舉一個(gè)類比來說:一個(gè)人類家族的發(fā)展是一個(gè)完整過程,需要經(jīng)過數(shù)代人的努力,每一代都會(huì)以接著上一代的成果繼續(xù)發(fā)展,所以每一代都是迭代。
2.1 迭代器
(1)怎么判斷是否可迭代
作為一門設(shè)計(jì)語言,Python提供了許多必要的數(shù)據(jù)類型,例如基本數(shù)據(jù)類型int、bool、str,還有容器類型list、tuple、dict、set。這些類型當(dāng)中,有些是可迭代的,有些不可迭代,怎么判斷呢?
在Python中,我們把所有可以迭代的對(duì)象統(tǒng)稱為可迭代對(duì)象,有一個(gè)類專門與之對(duì)應(yīng):Iterable。所以,要判斷一個(gè)類是否可迭代,只要判斷是否是Iterable類的實(shí)例即可
。
>>> from collections import Iterable >>> isinstance(123, Iterable) False >>>
isinstance(True, Iterable) False>>> isinstance('abc', Iterable) True >>>
isinstance([], Iterable) True>>> isinstance({}, Iterable) True >>>
isinstance((), Iterable) True
所以,整型、布爾不可迭代,字符串、列表、字典、元組可迭代。
怎么讓一個(gè)對(duì)象可迭代呢?畢竟,很多時(shí)候,我們需要用到的對(duì)象不止Python內(nèi)置的這些數(shù)據(jù)類型,還有自定義的數(shù)據(jù)類型。答案就是實(shí)現(xiàn)__iter__()方法,
只要一個(gè)對(duì)象定義了__iter__()方法,那么它就是可迭代對(duì)象。
from collections.abc import Iterable class A(): def __iter__(self): pass print(
'A()是可迭代對(duì)象嗎:',isinstance(A(),Iterable))
結(jié)果輸出為:
A()是可迭代對(duì)象嗎: True
瞧,我們?cè)赺_iter__()方法里面甚至沒寫任何東西,反正我們?cè)陬怉中定義則__iter__()方法,那么,它就是一個(gè)可迭代對(duì)象。
重要的事情說3遍:
只要一個(gè)對(duì)象定義了__iter__()方法,那么它就是可迭代對(duì)象。
只要一個(gè)對(duì)象定義了__iter__()方法,那么它就是可迭代對(duì)象。
只要一個(gè)對(duì)象定義了__iter__()方法,那么它就是可迭代對(duì)象。
2.2 迭代器
迭代器是對(duì)可迭代對(duì)象的改造升級(jí),上面說過,一個(gè)對(duì)象定義了__iter__()方法,那么它就是可迭代對(duì)象,進(jìn)一步地,
如果一個(gè)對(duì)象同時(shí)實(shí)現(xiàn)了__iter__()和__next()__()方法,那么它就是迭代器。
來,跟我讀三遍:
如果一個(gè)對(duì)象同時(shí)實(shí)現(xiàn)了__iter__()和__next()__()方法,那么它就是迭代器。
如果一個(gè)對(duì)象同時(shí)實(shí)現(xiàn)了__iter__()和__next()__()方法,那么它就是迭代器。
如果一個(gè)對(duì)象同時(shí)實(shí)現(xiàn)了__iter__()和__next()__()方法,那么它就是迭代器。
在Python中,也有一個(gè)類與迭代器對(duì)應(yīng):Iterator。所以,要判斷一個(gè)類是否是迭代器,只要判斷是否是Iterator類的實(shí)例即可。
from collections.abc import Iterable from collections.abc import Iterator class
B():def __iter__(self): pass def __next__(self): pass print('B()是可迭代對(duì)象嗎:'
,isinstance(B(), Iterable))print('B()是迭代器嗎:',isinstance(B(), Iterator))
結(jié)果輸出如下:
B()是可迭代對(duì)象嗎: True
B()是迭代器嗎: True
可見,迭代器一定是可迭代對(duì)象,但可迭代對(duì)象不一定是迭代器。
所以整型、布爾一定不是迭代器,因?yàn)樗麄冞B可迭代對(duì)象都算不上。那么,字符串、列表、字典、元組是迭代器嗎?猜猜!
>>> from collections.abc import Iterator >>> isinstance('abc', Iterator) False
>>> isinstance([], Iterator) False >>> isinstance({}, Iterator) False >>>
isinstance((), Iterator) False
驚不驚喜,意不意外,字符串、列表、字典、元組都不是迭代器。那為什么它們可以在for循環(huán)中遍歷呢?而且,我想,看到這里,就算你已經(jīng)可以在形式上區(qū)分可迭代對(duì)象和迭代器,但是你可能會(huì)問,這有什么卵用嗎?確實(shí),沒多少卵用,因?yàn)槲覀冞€不知道__iter__()、__next__()到底是個(gè)什么鬼東西。
接下來,我們通過繼續(xù)探究for循環(huán)的本質(zhì)來解答這些問題。
2.3 for循環(huán)的本質(zhì)
說到__iter__()和__next__()方法,就很有必要介紹一下iter()和next()方法了。
(1)iter()與__iter__()
__iter__()的作用是返回一個(gè)迭代器,雖然上面說過,只要實(shí)現(xiàn)了__iter__()方法就是可迭代對(duì)象,但是,沒有實(shí)現(xiàn)功能(返回迭代器)總歸是有問題的,就像一個(gè)村長,當(dāng)選之后,那就是村長了,但是如果尸位素餐不做事,那總是有問題的。
__iter__()方法畢竟是一個(gè)特殊方法,不適合直接調(diào)用,所以Python提供了iter()方法。iter()是Python提供的一個(gè)內(nèi)置方法,可以不用導(dǎo)入,直接調(diào)用即可。
from collections.abc import Iterator class A(): def __iter__(self): print('
A類的__iter__()方法被調(diào)用') return B() class B(): def __iter__(self): print('
B類的__iter__()方法被調(diào)用') return self def __next__(self): pass a = A() print('
對(duì)A類對(duì)象調(diào)用iter()方法前,a是迭代器嗎:', isinstance(a, Iterator)) a1 = iter(a) print('
對(duì)A類對(duì)象調(diào)用iter()方法后,a1是迭代器嗎:', isinstance(a1, Iterator)) b = B() print('
對(duì)B類對(duì)象調(diào)用iter()方法前,b是迭代器嗎:', isinstance(b, Iterator)) b1 = iter(b) print('
對(duì)B類對(duì)象調(diào)用iter()方法后,b1是迭代器嗎:', isinstance(b1, Iterator))
運(yùn)行結(jié)果如下:
對(duì)A類對(duì)象調(diào)用iter()方法前,a是迭代器嗎: False
A類的__iter__()方法被調(diào)用
對(duì)A類對(duì)象調(diào)用iter()方法后,a1是迭代器嗎: True
對(duì)B類對(duì)象調(diào)用iter()方法前,b是迭代器嗎: True
B類的__iter__()方法被調(diào)用
對(duì)B類對(duì)象調(diào)用iter()方法后,b1是迭代器嗎: True
對(duì)于B類,因?yàn)锽類本身就是迭代器,所以可以直接返回B類的實(shí)例,也就是說self,當(dāng)然,你要是返回其他迭代器也沒毛病。對(duì)于類A,它只是一個(gè)可迭代對(duì)象,__iter__()方法需要返回一個(gè)迭代器,所以返回了B類的實(shí)例,如果返回的不是一個(gè)迭代器,調(diào)用iter()方法時(shí)就會(huì)報(bào)以下錯(cuò)誤:
TypeError: iter() returned non-iterator of type 'A'
(2)next()與__next__()
__next__()的作用是返回遍歷過程中的下一個(gè)元素,如果沒有下一個(gè)元素則主動(dòng)拋出StopIteration異常。而next()就是Python提供的一個(gè)用于調(diào)用__next__()方法的內(nèi)置方法。
下面,我們通過next()方法來遍歷一個(gè)list:
>>> list_1 = [1, 2, 3] >>> next(list_1) Traceback (most recent call last): File
"<pyshell#19>", line 1, in <module> next(list_1) TypeError: 'list' object is not
an iterator>>> list_2 = iter(list_1) >>> next(list_2) 1 >>> next(list_2) 2 >>>
next(list_2)3 >>> next(list_2) Traceback (most recent call last): File "
<pyshell#24>", line 1, in <module> next(list_2) StopIteration
因?yàn)榱斜碇皇强傻鷮?duì)象,不是迭代器,所以對(duì)list_1直接調(diào)用next()方法會(huì)產(chǎn)生異常。對(duì)list_1調(diào)用iter()后就可以獲得是迭代器的list_2,對(duì)list_2每一次調(diào)用next()方法都會(huì)取出一個(gè)元素,當(dāng)沒有下一個(gè)元素時(shí)繼續(xù)調(diào)用next()就拋出了StopIteration異常。
>>> class A(): def __init__(self, lst): self.lst = lst def __iter__
(self): print('A.__iter__()方法被調(diào)用') return B(self.lst) >>> class B(): def
__init__(self, lst): self.lst = lst self.index = 0 def __iter__(self):
print('B.__iter__()方法被調(diào)用') return self def __next__(self): try: print('
B.__next__()方法被調(diào)用') value = self.lst[self.index] self.index += 1 return
value except IndexError: raise StopIteration() >>> a = A([1, 2, 3]) >>>
a1 = iter(a) A.__iter__()方法被調(diào)用 >>> next(a1) B.__next__()方法被調(diào)用 1 >>> next(a1) B.
__next__()方法被調(diào)用 2 >>> next(a1) B.__next__()方法被調(diào)用 3 >>> next(a1) B.__next__
()方法被調(diào)用 Traceback (most recent call last): File"<pyshell#78>", line 11, in
__next__ value = self.lst[self.index] IndexError: list index out of range
During handling of the above exception, another exception occurred: Traceback
(most recent call last): File"<pyshell#84>", line 1, in <module> next(a1) File "
<pyshell#78>", line 15, in __next__ raise StopIteration() StopIteration
A類實(shí)例化出來的實(shí)例a只是可迭代對(duì)象,不是迭代器,調(diào)用iter()方法后,返回了一個(gè)B類的實(shí)例a1,每次對(duì)a1調(diào)用next()方法,都用調(diào)用B類的__next__()方法。
接下來,我們用for循環(huán)遍歷一下A類實(shí)例:
>>> for i in A([1, 2, 3]): print('for循環(huán)中取出值:',i) A.__iter__()方法被調(diào)用 B.
__next__()方法被調(diào)用 for循環(huán)中取出值: 1 B.__next__()方法被調(diào)用 for循環(huán)中取出值: 2 B.__next__()方法被調(diào)用
for循環(huán)中取出值:3 B.__next__()方法被調(diào)用
通過for循環(huán)對(duì)一個(gè)可迭代對(duì)象進(jìn)行迭代時(shí),for循環(huán)內(nèi)部機(jī)制會(huì)自動(dòng)通過調(diào)用iter()方法執(zhí)行可迭代對(duì)象內(nèi)部定義的__iter__()方法來獲取一個(gè)迭代器,然后一次又一次得迭代過程中通過調(diào)用next()方法執(zhí)行迭代器內(nèi)部定義的__next__()方法獲取下一個(gè)元素,當(dāng)沒有下一個(gè)元素時(shí),for循環(huán)自動(dòng)捕獲并處理StopIteration異常。如果你還沒明白,請(qǐng)看下面用while循環(huán)實(shí)現(xiàn)for循環(huán)功能,整個(gè)過程、原理都是一樣的:
>>> a = A([1, 2, 3]) >>> a1 = iter(a) A.__iter__()方法被調(diào)用 >>> while True: try
: i= next(a1) print('for循環(huán)中取出值:', i) except StopIteration:
break B.__next__()方法被調(diào)用 for循環(huán)中取出值: 1 B.__next__()方法被調(diào)用 for循環(huán)中取出值: 2 B.
__next__()方法被調(diào)用 for循環(huán)中取出值: 3 B.__next__()方法被調(diào)用 作為一個(gè)迭代器,B類對(duì)象也可以通過for循環(huán)來迭代: >>>
for i in B([1, 2, 3]): print('for循環(huán)中取出值:',i) B.__iter__()方法被調(diào)用 B.__next__
()方法被調(diào)用 for循環(huán)中取出值:1 B.__next__()方法被調(diào)用 for循環(huán)中取出值: 2 B.__next__()方法被調(diào)用 for循環(huán)中取出值:
3 B.__next__()方法被調(diào)用 看出來了嗎?這就是for循環(huán)的本質(zhì)。
3 生成器
3.1 迭代器與生成器
如果一個(gè)函數(shù)體內(nèi)部使用yield關(guān)鍵字,這個(gè)函數(shù)就稱為生成器函數(shù),生成器函數(shù)調(diào)用時(shí)產(chǎn)生的對(duì)象就是生成器。生成器是一個(gè)特殊的迭代器,在調(diào)用該生成器函數(shù)時(shí),Python會(huì)自動(dòng)在其內(nèi)部添加__iter__()方法和__next__()方法。把生成器傳給
next() 函數(shù)時(shí), 生成器函數(shù)會(huì)向前繼續(xù)執(zhí)行, 執(zhí)行到函數(shù)定義體中的下一個(gè) yield 語句時(shí), 返回產(chǎn)出的值, 并在函數(shù)定義體的當(dāng)前位置暫停,
下一次通過next()方法執(zhí)行生成器時(shí),又從上一次暫停位置繼續(xù)向下……,最終, 函數(shù)內(nèi)的所有yield都執(zhí)行完,如果繼續(xù)通過yield調(diào)用生成器,
則會(huì)拋出StopIteration 異?!@一點(diǎn)與迭代器協(xié)議一致。
>>> from collections.abc import Iterable >>> from collections.abc import
Iterator>>> def gen(): print('第1次執(zhí)行') yield 1 print('第2次執(zhí)行') yield 2
print('第3次執(zhí)行') yield 3 >>> g = gen() >>> isinstance(g, Iterable) True >>>
isinstance(g, Iterator) True>>> g <generator object gen at 0x0000021CE9A39A98>
>>> next(g) 第1次執(zhí)行 1 >>> next(g) 第2次執(zhí)行 2 >>> next(g) 第3次執(zhí)行 3 >>> next(g)
Traceback (most recent call last): File"<pyshell#120>", line 1, in <module>
next(g) StopIteration
可以看到,生成器的執(zhí)行機(jī)制與迭代器是極其相似的,生成器本就是迭代器,只不過,有些特殊。那么,生成器特殊在哪呢?或者說,有了迭代器,為什么還要用生成器?
從上面的介紹和代碼中可以看出,生成器采用的是一種惰性計(jì)算機(jī)制,一次調(diào)用也只會(huì)產(chǎn)生一個(gè)值,它不會(huì)將所有的值一次性返回給你,你需要一個(gè)那就調(diào)用一次next()方法取一個(gè)值,這樣做的好處是如果元素有很多(數(shù)以億計(jì)甚至更多),如果用列表一次性返回所有元素,那么會(huì)消耗很大內(nèi)存,如果我們只是想要對(duì)所有元素依次一個(gè)一個(gè)取出來處理,那么,使用生成器就正好,一次返回一個(gè),并不會(huì)占用太大內(nèi)存。
舉個(gè)例子,假設(shè)我們現(xiàn)在要取1億以內(nèi)的所有偶數(shù),如果用列表來實(shí)現(xiàn),代碼如下:
def fun_list(): index = 1 temp_list = [] while index < 100000000: if index % 2
== 0: temp_list.append(index) print(index) index += 1 return temp_list
上面程序會(huì)先獲取所有符合要求的偶數(shù),然后一次性返回。如果你運(yùn)行了代碼,你就會(huì)發(fā)現(xiàn)兩個(gè)問題——運(yùn)行時(shí)間很長、消耗很多內(nèi)存。
有時(shí)候,我們并不一定需要一次性獲得所有的對(duì)象,需要一個(gè)使用一個(gè)就可以,這樣的話,可以用生成器來實(shí)現(xiàn):
>>> def fun_gen(): index = 1 while index < 100000000: if index % 2 ==
0: yield index index += 1 >>> fun_gen() <generator object fun_gen at
0x00000222DC2F4360> >>> g = fun_gen() >>> next(g) 2 >>> next(g) 4 >>> next(g) 6
看到了嗎?對(duì)生成器沒執(zhí)行一次next()方法,就會(huì)返回一個(gè)元素,這樣的話無論在速度上還是機(jī)器性能消耗上都會(huì)好很多。如果你還沒感受到生成器的優(yōu)勢,我再說一個(gè)應(yīng)用場景,假如需要取出遠(yuǎn)程數(shù)據(jù)庫中的100萬條記錄進(jìn)行處理,如果一次性獲取所有記錄,網(wǎng)絡(luò)帶寬、內(nèi)存都會(huì)有很大消耗,但是如果使用生成器,就可以取一條,就在本地處理一條。
不過,生成器也有不足,正因?yàn)椴捎昧硕栊杂?jì)算,你不會(huì)知道下一個(gè)元素是什么,更不會(huì)知道后面還有多少元素,所以,對(duì)于列表、元組等結(jié)構(gòu),我們能調(diào)用len()方法獲知長度,但是對(duì)于生成器卻不能。
總結(jié)一下迭代器與生成器的異同:
(1)生成器是一種特殊的迭代器,擁有迭代器的所有特性;
(2)迭代器使用return返回值而生成器使用yield返回值每一次對(duì)生成器執(zhí)行next()都會(huì)在yield處暫停;
(3)迭代器和生成器雖然都執(zhí)行next()方法時(shí)返回下一個(gè)元素,迭代器在實(shí)例化前就已知所有元素,但是采用惰性計(jì)算機(jī)制,共有多少元素,下一個(gè)元素是什么都是未知的,每一次對(duì)生成器對(duì)象執(zhí)行next()方法才會(huì)產(chǎn)生下一個(gè)元素。
3.2 生成器解析式
使用過列表解析式嗎?語法格式為:[返回值 for 元素 in 可迭代對(duì)象 if 條件]
看下面代碼:
>>> li = [] >>> for i in range(5): if i%2==0: li.append(i**2) >>> li [0,
4, 16]
我們可以用列表解析式實(shí)現(xiàn)同樣功能:
>>> li = [i**2 for i in range(5) if i%2==0] >>> li [0, 4, 16] >>> type(li) <
class 'list'>
很簡單對(duì)不對(duì)?簡潔了很多,返回的li就是一個(gè)列表??瓤取}了,我們要說的是生成器解析式,而且我相信打開我這篇博文的同學(xué)大多都熟悉列表解析式,回歸正題。
生成器解析式語法格式為:(返回值 for 元素 in 可迭代對(duì)象 if 條件)
你沒看錯(cuò),跟列表解析式相比,生成器解析式只是把方括號(hào)換成了原括號(hào)。來感受一下:
>>> g = (i**2 for i in range(5) if i%2==0) >>> g <generator object <genexpr>
at 0x00000222DC2F4468> >>> next(g) 0 >>> next(g) 4 >>> next(g) 16 >>> next(g)
Traceback (most recent call last): File"<pyshell#38>", line 1, in <module>
next(g) StopIteration
可以看到,生成器解析式返回的就是一個(gè)生成器對(duì)象,換句話說生成器解析式是生成器的一種定義方式,這種方式簡單快捷,當(dāng)然實(shí)現(xiàn)的功能不能太復(fù)雜。
4 總結(jié)
本文全面總結(jié)了Python中可迭代對(duì)象、迭代器、生成器知識(shí),我相信,只要你認(rèn)真消化我這篇博文,就能深刻領(lǐng)悟迭代器生成器。
? ?
熱門工具 換一換