https://zhuanlan.zhihu.com/p/26123333
要完全理解透生成器,需要我們先掌握三個(gè)概念:
可迭代對(duì)象(Iterable)
迭代器(Iterator)
迭代(Iteration)
放一張圖來(lái)理解浇借,來(lái)自這里
額外提到了容器(container)论矾,說(shuō)的是我們的集合類對(duì)象,如 list沛简、set齐鲤、dict斥废,它們將多個(gè)元素組織在一起,這些對(duì)象就可以稱為 container给郊。
可迭代對(duì)象:
可直接作用于for循環(huán)的對(duì)象統(tǒng)稱為Iterable 牡肉。具體的實(shí)現(xiàn)是,Python 中的對(duì)象只要定義了__iter__方法(該方法返回一個(gè)迭代器對(duì)象)淆九,或者定義了支持下標(biāo)索引的__getitem__方法统锤,那么這個(gè)對(duì)象就是可迭代對(duì)象。
>>>fromcollectionsimportIterable>>>isinstance([],Iterable)True>>>isinstance({},Iterable)True>>>isinstance([xforxinrange(10)],Iterable)True
迭代器:
可作用于next()函數(shù)的對(duì)象都是Iterator炭庙。具體的實(shí)現(xiàn)是饲窿,任何對(duì)象只要定義了__iter__和__next__方法,那就是迭代器對(duì)象焕蹄;迭代器表示一個(gè)惰性計(jì)算的序列逾雄,需要__iter__返回迭代器自身,__next__返回迭代器中的下一個(gè)值腻脏,迭代到結(jié)尾時(shí)引發(fā)
StopIteration
異常鸦泳;也就是說(shuō)迭代器在遍歷集合時(shí),并不是將所有的元素事先都準(zhǔn)備好永品,而是迭代到某個(gè)元素時(shí)才去計(jì)算該元素做鹰,利用這一特性我們可以去遍歷一些巨大的集合,之前總結(jié)的函數(shù)式編程中鼎姐,map钾麸,reduce,filter函數(shù)返回的就是一個(gè)新的迭代器炕桨。
還有一點(diǎn)需要明確的饭尝,迭代器都是可迭代對(duì)象,可迭代對(duì)象可以通過(guò)iter()返回一個(gè)新的迭代器谋作。
>>>L=[1,2,3,4,5]>>>'__iter__'indir(L)True>>>'__next__'indir(L)False>>>newL=iter(L)>>>'__next__'indir(newL)True>>>newL.__next__()1>>>newL.__next__()2# 定義斐波拉契數(shù)的迭代器>>>classfib(object):...def__init__(self):...self.prev=0...self.curr=1...def__iter__(self):...returnself...def__next__(self):...value=self.curr...self.curr+=self.prev...self.prev=value...returnvalue...>>>f=fib()>>>foriinf:...ifi>20:break...print(i)...11235813
從上面的迭代操作中芋肠,可以看出 for 循環(huán)其實(shí)是調(diào)用__iter__獲得迭代器,再調(diào)用__next__獲取元素遵蚜,迭代器內(nèi)部狀態(tài)保存在當(dāng)前實(shí)例對(duì)象的prev以及cur屬性中帖池,在下一次調(diào)用中將使用這兩個(gè)屬性。每次調(diào)用next()方法都會(huì)執(zhí)行以下兩步操作:
修改狀態(tài)吭净,以便下次調(diào)用next()方法
計(jì)算當(dāng)前調(diào)用的結(jié)果
迭代器的使用非常普通睡汹,Python的內(nèi)置庫(kù)itertools就是專門返回迭代器對(duì)象的,這篇博文專門介紹itertools庫(kù)的寂殉,我從中列舉了一些:
# 累加>>>importitertools>>>a=itertools.accumulate(range(10))>>>a>>>print(list(a))[0,1,3,6,10,15,21,28,36,45]# 連接列表或迭代器>>>c=itertools.chain(range(3),range(4),[0,1,2,3,4])>>>print(list(c))[0,1,2,0,1,2,3,0,1,2,3,4]# 按照真值表篩選元素>>>x=itertools.compress(range(5),(True,False,True,True,False))>>>print(list(x))[0,2,3]# 計(jì)數(shù)器,可以指定起始位置和步長(zhǎng)>>>x=itertools.count(start=20,step=-1)>>>print(list(itertools.islice(x,0,10,1)))[20,19,18,17,16,15,14,13,12,11]# 按照分組函數(shù)的值對(duì)元素進(jìn)行分組>>>x=itertools.groupby(range(10),lambdax:x<5orx>8)>>>forcondition,numbersinx:...print(condition,list(numbers))True[0,1,2,3,4]False[5,6,7,8]True[9]# 類似map>>>x=itertools.starmap(str.islower,'aBCDefGhI')>>>print(list(x))[True,False,False,False,True,True,False,True,False]
生成器:
有了前面的鋪墊囚巴,我們就能更好地理解生成器了。生成器是什么?說(shuō)白了生成器就是一種特殊的迭代器彤叉,不過(guò)它的實(shí)現(xiàn)方式更為簡(jiǎn)單優(yōu)雅庶柿,同樣我們可以明確的是,任何生成器都是迭代器秽浇,生成器也是一個(gè)惰性計(jì)算的序列浮庐。
我們來(lái)看看生成器的兩種定義方式:
1、生成器表達(dá)式:
>>>[i*iforiinrange(5)]# 注意 Python3 中 range函數(shù)是迭代器[0,1,4,9,16]# 根據(jù)列表生成式柬焕,只需要簡(jiǎn)單修改就可以定義生成器>>>(i*iforiinrange(3))at0x7f59ed8fc3b8>
2审残、另一種定義復(fù)雜推導(dǎo)算法的生成器需要引入一個(gè)強(qiáng)大的關(guān)鍵字yield:
# 斐波那契序列的生成器函數(shù)>>> def fib():...? ? prev = curr = 1...? ? yield prev #1...? ? yield curr #2...? ? while True:...? ? ? ? prev, curr = curr, prev + curr...? ? ? ? yield curr... >>> f = fib()>>> f>>> for i in f:? ? # 還可以使用 next() 遍歷生成器...? ? if i > 20: break...? ? print(i)... 11235813
分析一下流程:
調(diào)用生成器函數(shù)時(shí)只返回一個(gè) generator 對(duì)象 f,函數(shù)并沒有執(zhí)行斑举;
通過(guò) for 循環(huán)生成器才開始執(zhí)行搅轿,執(zhí)行到 #1 yield prev 處,返回 yield 處的參數(shù) prev富玷,此時(shí)就打印出了1璧坟;
繼續(xù) for 循環(huán),生成器函數(shù)將在上一次停止的語(yǔ)句處繼續(xù)執(zhí)行凌彬,遇到 #2 yield curr 返回沸柔,此時(shí)又打印出了1循衰;
如此反復(fù)铲敛,直到i大于20跳出循環(huán)結(jié)束調(diào)用。
對(duì)比迭代器和生成器会钝,實(shí)現(xiàn)同樣的功能伐蒋,生成器會(huì)顯得更加優(yōu)雅簡(jiǎn)潔。
迭代
一句話總結(jié)迭代:按照一定的順序逐個(gè)訪問(wèn)容器中每一個(gè)元素的過(guò)程迁酸;也就是我們折騰斐波那契序列的過(guò)程
限于篇幅先鱼,生成器就介紹到這里,但生成器的威力遠(yuǎn)不止此奸鬓,下一篇將通過(guò)生成器和 yield 引出協(xié)程和異步IO等焙畔。