深入講解Python中的迭代器和生成器
在Python中,很多對象都是可以通過for語句來直接遍歷的,例如list、string、dict等等,這些對象都可以被稱為可迭代對象。至于說哪些對象是可以被迭代訪問的,就要了解一下迭代器相關(guān)的知識了。
迭代器
迭代器對象要求支持迭代器協(xié)議的對象,在Python中,支持迭代器協(xié)議就是實現(xiàn)對象的__iter__()和next()方法。其中__iter__()方法返回迭代器對象本身;next()方法返回容器的下一個元素,在結(jié)尾時引發(fā)StopIteration異常。
__iter__()和next()方法
這兩個方法是迭代器最基本的方法,一個用來獲得迭代器對象,一個用來獲取容器中的下一個元素。
對于可迭代對象,可以使用內(nèi)建函數(shù)iter()來獲取它的迭代器對象:

例子中,通過iter()方法獲得了list的迭代器對象,然后就可以通過next()方法來訪問list中的元素了。當(dāng)容器中沒有可訪問的元素后,next()方法將會拋出一個StopIteration異常終止迭代器。
其實,當(dāng)我們使用for語句的時候,for語句就會自動的通過__iter__()方法來獲得迭代器對象,并且通過next()方法來獲取下一個元素。
自定義迭代器
了解了迭代器協(xié)議之后,就可以自定義迭代器了。
下面例子中實現(xiàn)了一個MyRange的類型,這個類型中實現(xiàn)了__iter__()方法,通過這個方法返回對象本身作為迭代器對象;同時,實現(xiàn)了next()方法用來獲取容器中的下一個元素,當(dāng)沒有可訪問元素后,就拋出StopIteration異常。
class MyRange(object): def __init__(self, n): self.idx = 0 self.n = n def __iter__(self): return self def next(self): if self.idx < self.n: val = self.idx self.idx += 1 return val else: raise StopIteration() class MyRange(object): def __init__(self, n): self.idx = 0 self.n = n def __iter__(self): return self def next(self): if self.idx < self.n: val = self.idx self.idx += 1 return val else: raise StopIteration()
這個自定義類型跟內(nèi)建函數(shù)xrange很類似,看一下運行結(jié)果:
myRange = MyRange(3) for i in myRange: print i

迭代器和可迭代對象
在上面的例子中,myRange這個對象就是一個可迭代對象,同時它本身也是一個迭代器對象。
看下面的代碼,對于一個可迭代對象,如果它本身又是一個迭代器對象,就會有下面的 問題,就沒有辦法支持多次迭代。

為了解決上面的問題,可以分別定義可迭代類型對象和迭代器類型對象;然后可迭代類型對象的__iter__()方法可以獲得一個迭代器類型的對象。看下面的實現(xiàn):
class Zrange: def __init__(self, n): self.n = n def __iter__(self): return ZrangeIterator(self.n) class ZrangeIterator: def __init__(self, n): self.i = 0 self.n = n def __iter__(self): return self def next(self): if self.i < self.n: i = self.i self.i += 1 return i else: raise StopIteration() zrange = Zrange(3) print zrange is iter(zrange) print [i for i in zrange] print [i for i in zrange]
代碼的運行結(jié)果為:

其實,通過下面代碼可以看出,list類型也是按照上面的方式,list本身是一個可迭代對象,通過iter()方法可以獲得list的迭代器對象:

生成器
在Python中,使用生成器可以很方便的支持迭代器協(xié)議。生成器通過生成器函數(shù)產(chǎn)生,生成器函數(shù)可以通過常規(guī)的def語句來定義,但是不用return返回,而是用yield一次返回一個結(jié)果,在每個結(jié)果之間掛起和繼續(xù)它們的狀態(tài),來自動實現(xiàn)迭代協(xié)議。
也就是說,yield是一個語法糖,內(nèi)部實現(xiàn)支持了迭代器協(xié)議,同時yield內(nèi)部是一個狀態(tài)機(jī),維護(hù)著掛起和繼續(xù)的狀態(tài)。
下面看看生成器的使用:

在這個例子中,定義了一個生成器函數(shù),函數(shù)返回一個生成器對象,然后就可以通過for語句進(jìn)行迭代訪問了。
其實,生成器函數(shù)返回生成器的迭代器。 “生成器的迭代器”這個術(shù)語通常被稱作”生成器”。要注意的是生成器就是一類特殊的迭代器。作為一個迭代器,生成器必須要定義一些方法,其中一個就是next()。如同迭代器一樣,我們可以使用next()函數(shù)來獲取下一個值。
生成器執(zhí)行流程
下面就仔細(xì)看看生成器是怎么工作的。
從上面的例子也可以看到,生成器函數(shù)跟普通的函數(shù)是有很大差別的。
結(jié)合上面的例子我們加入一些打印信息,進(jìn)一步看看生成器的執(zhí)行流程:

通過結(jié)果可以看到:
當(dāng)調(diào)用生成器函數(shù)的時候,函數(shù)只是返回了一個生成器對象,并沒有 執(zhí)行。
當(dāng)next()方法第一次被調(diào)用的時候,生成器函數(shù)才開始執(zhí)行,執(zhí)行到y(tǒng)ield語句處停止
next()方法的返回值就是yield語句處的參數(shù)(yielded value)
當(dāng)繼續(xù)調(diào)用next()方法的時候,函數(shù)將接著上一次停止的yield語句處繼續(xù)執(zhí)行,并到下一個yield處停止;如果后面沒有yield就拋出StopIteration異常。
生成器表達(dá)式
在開始介紹生成器表達(dá)式之前,先看看我們比較熟悉的列表解析( List comprehensions),列表解析一般都是下面的形式。
[expr for iter_var in iterable if cond_expr]
迭代iterable里所有內(nèi)容,每一次迭代后,把iterable里滿足cond_expr條件的內(nèi)容放到iter_var中,再在表達(dá)式expr中應(yīng)該iter_var的內(nèi)容,最后用表達(dá)式的計算值生成一個列表。
例如,生成一個list來保護(hù)50以內(nèi)的所以奇數(shù):
[i for i in range(50) if i%2]
生成器表達(dá)式是在python2.4中引入的,當(dāng)序列過長, 而每次只需要獲取一個元素時,應(yīng)當(dāng)考慮使用生成器表達(dá)式而不是列表解析。生成器表達(dá)式的語法和列表解析一樣,只不過生成器表達(dá)式是被()括起來的,而不是[],如下:
(expr for iter_var in iterable if cond_expr)
看一個例子:

生成器表達(dá)式并不是創(chuàng)建一個列表, 而是返回一個生成器,這個生成器在每次計算出一個條目后,把這個條目”產(chǎn)生”(yield)出來。 生成器表達(dá)式使用了”惰性計算”(lazy evaluation),只有在檢索時才被賦值(evaluated),所以在列表比較長的情況下使用內(nèi)存上更有效。
繼續(xù)看一個例子:

從這個例子中可以看到,生成器表達(dá)式產(chǎn)生的生成器,它自身是一個可迭代對象,同時也是迭代器本身。
遞歸生成器
生成器可以向函數(shù)一樣進(jìn)行遞歸使用的,下面看一個簡單的例子,對一個序列進(jìn)行全排列:
def permutations(li):
if len(li) == 0:
yield li
else:
for i in range(len(li)):
li[0], li[i] = li[i], li[0]
for item in permutations(li[1:]):
yield [li[0]] + item
for item in permutations(range(3)):
print item
def permutations(li):
if len(li) == 0:
yield li
else:
for i in range(len(li)):
li[0], li[i] = li[i], li[0]
for item in permutations(li[1:]):
yield [li[0]] + item
for item in permutations(range(3)):
print item
生成器的send()和close()方法
生成器中還有兩個很重要的方法:send()和close()。
send(value):
從前面了解到,next()方法可以恢復(fù)生成器狀態(tài)并繼續(xù)執(zhí)行,其實send()是除next()外另一個恢復(fù)生成器的方法。
Python 2.5中,yield語句變成了yield表達(dá)式,也就是說yield可以有一個值,而這個值就是send()方法的參數(shù),所以send(None)和next()是等效的。同樣,next()和send()的返回值都是yield語句處的參數(shù)(yielded value)
關(guān)于send()方法需要注意的是:調(diào)用send傳入非None值前,生成器必須處于掛起狀態(tài),否則將拋出異常。也就是說,第一次調(diào)用時,要使用next()語句或send(None),因為沒有yield語句來接收這個值。
close():
這個方法用于關(guān)閉生成器,對關(guān)閉的生成器后再次調(diào)用next或send將拋出StopIteration異常。
下面看看這兩個方法的使用:

總結(jié)
本文介紹了Python迭代器和生成器的相關(guān)內(nèi)容。
- 通過實現(xiàn)迭代器協(xié)議對應(yīng)的__iter__()和next()方法,可以自定義迭代器類型。對于可迭代對象,for語句可以通過iter()方法獲取迭代器,并且通過next()方法獲得容器的下一個元素。
- 像列表這種序列類型的對象,可迭代對象和迭代器對象是相互獨立存在的,在迭代的過程中各個迭代器相互獨立;但是,有的可迭代對象本身又是迭代器對象,那么迭代器就沒法獨立使用。
- itertools模塊提供了一系列迭代器,能夠幫助用戶輕松地使用排列、組合、笛卡爾積或其他組合結(jié)構(gòu)。
- 生成器是一種特殊的迭代器,內(nèi)部支持了生成器協(xié)議,不需要明確定義__iter__()和next()方法。
- 生成器通過生成器函數(shù)產(chǎn)生,生成器函數(shù)可以通過常規(guī)的def語句來定義,但是不用return返回,而是用yield一次返回一個結(jié)果。
相關(guān)文章
解決windows下命令行執(zhí)行python3失效,會打開應(yīng)用商店問題
這篇文章主要介紹了解決windows下命令行執(zhí)行python3失效,會打開應(yīng)用商店問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-02-02
Python本地cache不當(dāng)使用導(dǎo)致內(nèi)存泄露的問題分析與解決
最近在項目開發(fā)中遇到了本地cache不當(dāng)使用導(dǎo)致的一個內(nèi)存泄露問題,所以本文主要分析了問題出現(xiàn)的原因已經(jīng)解決方法,需要的小伙伴可以參考下2023-08-08
使用Python進(jìn)行新浪微博的mid和url互相轉(zhuǎn)換實例(10進(jìn)制和62進(jìn)制互算)
我們在使用新浪微博API時,有時需要得到一個微博的url,但是如statuses/public_timeline等接口中取得的微博status的字段中并沒有包含2014-04-04

