怎么确定迭代器后面还有至少两个值_如何理解Python中的可迭代对象、迭代器和生成器
▍前言
在討論可迭代對象、迭代器和生成器之前,先說明一下迭代器模式(iterator pattern),維基百科這么解釋:
迭代器是一種最簡單也最常見的設(shè)計模式。它可以讓用戶透過特定的接口巡訪容器中的每一個元素而不用了解底層的實(shí)現(xiàn)。迭代是數(shù)據(jù)處理的基石。當(dāng)內(nèi)存中放不下數(shù)據(jù)集時,我們要找到一種惰性獲取數(shù)據(jù)的方式,即按需一次獲取一個數(shù)據(jù)項,這就是迭代器模式。▍序列可迭代的原因:iter函數(shù)
我們都知道序列是可迭代的。當(dāng)解釋器需要迭代對象x時,會自動調(diào)用iter(x)。
內(nèi)置的iter函數(shù)有以下作用:
- 檢查對象是否實(shí)現(xiàn)了__iter__方法,如果實(shí)現(xiàn)了就調(diào)用它,獲得一個迭代器。
- 如果沒有實(shí)現(xiàn)__iter__方法,但是實(shí)現(xiàn)了__getitem__方法,python會創(chuàng)建一個迭代器,嘗試按順序(從索引0開始)獲取元素。
- 如果嘗試失敗,python會拋出`TypeError`異常,通常會提示"C object is not iterable",其中C是目標(biāo)對象所屬的類。
截止到Python3.6,基本上所有的Python序列也都實(shí)現(xiàn)了__getitem__方法,這是保證任何序列都可迭代的原因。當(dāng)然標(biāo)準(zhǔn)的序列也都實(shí)現(xiàn)了__iter__方法,之所以對__getitem__也可以創(chuàng)建迭代器是為了向后兼容,未來可能不在這么做。
但是,從Python3.4開始,檢查x能否迭代,最準(zhǔn)確的方法是調(diào)用iter(x)函數(shù),如果不可迭代,再處理TypeError異常。
▍可迭代對象與迭代器關(guān)系
使用iter內(nèi)置函數(shù)可以獲取迭代器對象。也就是說,如果一個對象實(shí)現(xiàn)了能返回迭代器的__iter__方法,那么對象就是可迭代的,序列都可以迭代;實(shí)現(xiàn)了__getitem__方法,而且其參數(shù)是從零開始的索引,這種對象也是可迭代的。
因此可以明確可迭代對象和迭代器之間的關(guān)系:Python從可迭代的對象中獲取迭代器。
標(biāo)準(zhǔn)的迭代器接口有兩個方法,即:
- __next__:返回下一個可用元素,如果沒有元素,拋出StopIteration異常
- __iter__:返回self,以便在應(yīng)該使用可迭代對象的地方使用迭代器,比如for循環(huán)中。
因?yàn)榈髦恍鑏_next__和__iter__兩個方法,所以除了調(diào)用next()方法,以及捕獲StopIteration異常之外,沒有辦法檢查是否還有遺留的元素。此外,也沒有辦法還原迭代器。如果想再次迭代,那就要調(diào)用iter(…),傳入之前構(gòu)建迭代器的可迭代對象。
構(gòu)建可迭代對象和迭代器時經(jīng)常會出現(xiàn)錯誤,原因是混淆了兩者。要知道,可迭代的對象有個__iter__方法,調(diào)用該方法每次都實(shí)例化一個新的迭代器;而迭代器要實(shí)現(xiàn)__next__方法,返回單個元素,此外還要實(shí)現(xiàn)__iter__方法,返回迭代器本身(self),如圖。因此,迭代器可以迭代,但是可迭代的對象不是迭代器。
需要注意的是:
可迭代的對象必須實(shí)現(xiàn)__iter__方法,但不能實(shí)現(xiàn)__next__方法。另一方面,迭代器應(yīng)該一直可以迭代,迭代器的__iter__方法應(yīng)該返回自身。雖然可迭代對象和迭代器都有__iter__方法,但是兩者的功能不一樣,再次強(qiáng)調(diào)一下,可迭代對象的__iter__用于實(shí)例化一個迭代器對象,而迭代器中的__iter__用于返回迭代器本身,與__next__共同完成迭代器的迭代作用。?
▍生成器?
在Python中創(chuàng)建迭代器最方便的方法是使用生成器。生成器也是迭代器。生成器的語法類似于函數(shù),但是不返回值。為了顯示序列中的每一個元素,會使用yield語句。只要Python函數(shù)的定義體中有yield關(guān)鍵字,該函數(shù)就是生成器函數(shù)。調(diào)用生成器函數(shù)時,會返回一個生成器對象。
獲取生成器通常有兩種方式,生成器函數(shù)和生成器表達(dá)式。
生成器函數(shù)
def gen_123(): # 只要Python代碼中包含yield,該函數(shù)就是生成器函數(shù)yield 1 #生成器函數(shù)的定義體中通常都有循環(huán),不過這不是必要條件;此處重復(fù)使用了3次yieldyield 2yield 3 ? if __name__ == '__main__':print(gen_123) # 可以看出gen_123是函數(shù)對象# <function gen_123 at 0x10be19>print(gen_123()) # 函數(shù)調(diào)用時返回的是一個生成器對象# <generator object gen_123 at 0x10be31> ?for i in gen_123(): # 生成器是迭代器,會生成傳給yield關(guān)鍵字的表達(dá)式的值print(i) # 1# 2# 3 ?g = gen_123() # 為了仔細(xì)檢查,把生成器對象賦值給gprint(next(g)) # 1print(next(g)) # 2print(next(g)) # 3print(next(g)) # 生成器函數(shù)的定義體執(zhí)行完畢后,生成器對象會拋出異常。 # Traceback (most recent call last): # File "test.py", line 17, in <module> # print(next(g)) # StopIteration- 只要Python代碼中包含yield,該函數(shù)就是生成器函數(shù)
- 生成器函數(shù)的定義體中通常都有循環(huán),不過這不是必要條件;此處重復(fù)使用了3次yield
- 可以看出gen_123是函數(shù)對象
- 函數(shù)調(diào)用時返回的是一個生成器對象
- 生成器是迭代器,會生成傳給yield關(guān)鍵字的表達(dá)式的值
- 為了仔細(xì)檢查,把生成器對象賦值給g
- 因?yàn)間是迭代器,所以調(diào)用next(g)會獲取yield生成的下一個元素
- 生成器函數(shù)的定義體執(zhí)行完畢后,生成器對象會拋出異常。
生成器表達(dá)式
簡單的生成器函數(shù),可以替換成生成器表達(dá)式。生成器表達(dá)式可以理解為列表推導(dǎo)的惰性版本:不會迫切的構(gòu)建列表,而是返回一個生成器,按需惰性生成元素。也就是說,如果列表推導(dǎo)是制造工廠的列表,那么生成器表達(dá)式就是制造生成器的工廠。如下演示了一個簡單的生成器表達(dá)式,并且與列表推導(dǎo)做了對比。
In [1]: def gen_AB(): # 1...: print('start') ...: yield 'A' ...: print('continue')...: yield 'B' ...: print('end.') ...: ? In [2]: res1 = [x*3 for x in gen_AB()] # 2 start continue end. ? In [3]: for i in res1(): # 3...: print('-->', i) ...: AAA BBB ? In [4]: res2 = (x*3 for x in gen_AB()) # 4 ? In [5]: res2 # 5 <generator object <genexpr> at 0x106a07620> ? In [6]: for i in res2(): # 6...: print('-->', i)...: start --> A continue --> B end.- #1-創(chuàng)建gen_AB函數(shù)
- #2-列表推到迫切的迭代gen_AB()函數(shù)生成的生成器對象產(chǎn)出的元素:’A’和’B’。注意。下面輸出的是start、continue、end.。
- #3-for循環(huán)迭代列表推導(dǎo)生成的res1列表
- #4-把生成器表達(dá)式返回的值賦值給res2。只需調(diào)用gen_AB()函數(shù),雖然調(diào)用時會返回一個生成器,但是這里并不使用。
- #5-可以看出res2是一個生成器對象。
- #6-只有for循環(huán)迭代res2時,gen_AB函數(shù)的定義體才會真正執(zhí)行。for循環(huán)每次迭代時會隱式調(diào)用next(res2),前進(jìn)到gen_AB函數(shù)中的下一個yield語句。注意,gen_AB函數(shù)的輸出與for循環(huán)中print函數(shù)的輸出夾雜在一起。
生成器表達(dá)式是創(chuàng)建生成器的簡潔句法,這樣無需定義函數(shù)再調(diào)用。不過,生成器函數(shù)靈活的多,可以使用多個語句實(shí)現(xiàn)復(fù)雜的邏輯,也可以作為協(xié)程(后面有機(jī)會講到)使用。遇到簡單的情況時,可以使用生成器表達(dá)式,因?yàn)檫@樣掃一眼就知道代碼的作用。其實(shí)選擇那種句法很容易判斷:如果生成器表達(dá)式需要分行寫,傾向于定義成生成器函數(shù),以便提高可讀性;此外生成器函數(shù)有名稱,因此可以重用。
a = (i for i in range(10)) '__next__' in dir(a) # True '__iter__' in dir(a) # True以上可以看出,生成器也是一種迭代器!
前面我們提到了惰性計算。其實(shí),我們有時候使用生成器而不是傳統(tǒng)的函數(shù)時,正是因?yàn)槎栊杂嬎阌泻锰?--只計算需要的數(shù)據(jù),并且整個系列不需要一次性全部駐留在內(nèi)存中。事實(shí)上,生成器完全可以有效的生產(chǎn)數(shù)值的無限序列,舉一個例子,斐波那契數(shù)列是一個經(jīng)典的無限數(shù)字序列,下面用生成器可以產(chǎn)生這個無窮級數(shù):
def fibonacci():a = 0b = 1while True:yield afuture = a + ba = bb = future以上!
總結(jié)
以上是生活随笔為你收集整理的怎么确定迭代器后面还有至少两个值_如何理解Python中的可迭代对象、迭代器和生成器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 指向成员函数的指针有什么用_指针函数,函
- 下一篇: 在浏览器中内嵌word_关于项目浏览器内