Python 迭代器與生成器

https://zhuanlan.zhihu.com/p/26123333

要完全理解透生成器,需要我們先掌握三個概念:

可迭代對象(Iterable)

迭代器(Iterator)

迭代(Iteration)

放一張圖來理解,來自這里

額外提到了容器(container),說的是我們的集合類對象,如 list、set、dict,它們將多個元素組織在一起,這些對象就可以稱為 container。

可迭代對象:

可直接作用于for循環的對象統稱為Iterable 。具體的實現是,Python 中的對象只要定義了__iter__方法(該方法返回一個迭代器對象),或者定義了支持下標索引的__getitem__方法,那么這個對象就是可迭代對象。

>>>fromcollectionsimportIterable>>>isinstance([],Iterable)True>>>isinstance({},Iterable)True>>>isinstance([xforxinrange(10)],Iterable)True

迭代器:

可作用于next()函數的對象都是Iterator。具體的實現是,任何對象只要定義了__iter__和__next__方法,那就是迭代器對象;迭代器表示一個惰性計算的序列,需要__iter__返回迭代器自身,__next__返回迭代器中的下一個值,迭代到結尾時引發

StopIteration

異常;也就是說迭代器在遍歷集合時,并不是將所有的元素事先都準備好,而是迭代到某個元素時才去計算該元素,利用這一特性我們可以去遍歷一些巨大的集合,之前總結的函數式編程中,map,reduce,filter函數返回的就是一個新的迭代器。

還有一點需要明確的,迭代器都是可迭代對象,可迭代對象可以通過iter()返回一個新的迭代器。

>>>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# 定義斐波拉契數的迭代器>>>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 循環其實是調用__iter__獲得迭代器,再調用__next__獲取元素,迭代器內部狀態保存在當前實例對象的prev以及cur屬性中,在下一次調用中將使用這兩個屬性。每次調用next()方法都會執行以下兩步操作:

修改狀態,以便下次調用next()方法

計算當前調用的結果

迭代器的使用非常普通,Python的內置庫itertools就是專門返回迭代器對象的,這篇博文專門介紹itertools庫的,我從中列舉了一些:

# 累加>>>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]# 計數器,可以指定起始位置和步長>>>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]# 按照分組函數的值對元素進行分組>>>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]

生成器:

有了前面的鋪墊,我們就能更好地理解生成器了。生成器是什么?說白了生成器就是一種特殊的迭代器,不過它的實現方式更為簡單優雅,同樣我們可以明確的是,任何生成器都是迭代器,生成器也是一個惰性計算的序列。

我們來看看生成器的兩種定義方式:

1、生成器表達式:

>>>[i*iforiinrange(5)]# 注意 Python3 中 range函數是迭代器[0,1,4,9,16]# 根據列表生成式,只需要簡單修改就可以定義生成器>>>(i*iforiinrange(3))at0x7f59ed8fc3b8>

2、另一種定義復雜推導算法的生成器需要引入一個強大的關鍵字yield:

# 斐波那契序列的生成器函數>>> 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

分析一下流程:

調用生成器函數時只返回一個 generator 對象 f,函數并沒有執行;

通過 for 循環生成器才開始執行,執行到 #1 yield prev 處,返回 yield 處的參數 prev,此時就打印出了1;

繼續 for 循環,生成器函數將在上一次停止的語句處繼續執行,遇到 #2 yield curr 返回,此時又打印出了1;

如此反復,直到i大于20跳出循環結束調用。

對比迭代器和生成器,實現同樣的功能,生成器會顯得更加優雅簡潔。

迭代

一句話總結迭代:按照一定的順序逐個訪問容器中每一個元素的過程;也就是我們折騰斐波那契序列的過程

限于篇幅,生成器就介紹到這里,但生成器的威力遠不止此,下一篇將通過生成器和 yield 引出協程和異步IO等。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容