python里面有很多的以__開(kāi)始和結(jié)尾的函數(shù),利用它們可以完成很多復(fù)雜的邏輯代碼,而且提高了代碼的簡(jiǎn)潔性,本文主要總結(jié)了迭代器用到的魔術(shù)方法,并且主要以代碼例子進(jìn)行解釋。
__iter__ 和 __next__
其實(shí)這里需要引入一個(gè)概念,叫迭代器,常見(jiàn)的就是我們?cè)谑褂?code>for語(yǔ)句的時(shí)候,python內(nèi)部其實(shí)是把for后面的對(duì)象上使用了內(nèi)建函數(shù)iter,比如:
a = [1, 2, 3]
for i in a:
do_something()
其實(shí)在python內(nèi)部進(jìn)行了類(lèi)似如下的轉(zhuǎn)換:
a = [1, 2, 3]
for i in iter(a):
do_something()
那么iter返回的是什么呢,就是一個(gè)迭代對(duì)象,它主要映射到了類(lèi)里面的__iter__函數(shù),此函數(shù)返回的是一個(gè)實(shí)現(xiàn)了__next__的對(duì)象。注意理解這句話,比如:
class B(object):
def __next__(self):
raise StopIteration
class A(object):
def __iter__(self):
return B()
我們可以看見(jiàn),A這個(gè)類(lèi)實(shí)現(xiàn)了一個(gè)__iter__函數(shù),返回的是B()的實(shí)例對(duì)象,其中B里面實(shí)現(xiàn)了__next__這個(gè)函數(shù)。
下面引入幾個(gè)概念:
Iterable: 有迭代能力的對(duì)象,一個(gè)類(lèi),實(shí)現(xiàn)了__iter__,那么就認(rèn)為它有迭代能力,通常此函數(shù)必須返回一個(gè)實(shí)現(xiàn)了__next__的對(duì)象,如果自己實(shí)現(xiàn)了,你可以返回self,當(dāng)然這個(gè)返回值不是必須的;
Iterator: 迭代器(當(dāng)然也是Iterable),同時(shí)實(shí)現(xiàn)了__iter__和__next__的對(duì)象,缺少任何一個(gè)都不算是Iterator,比如上面例子中,A()可以是一個(gè)Iterable,但是A()和B()都不能算是和Iterator,因?yàn)?code>A只實(shí)現(xiàn)了__iter__,而B只實(shí)現(xiàn)了__next__()。
我們可以使用collections里面的類(lèi)型來(lái)進(jìn)行驗(yàn)證:
class B(object):
def __next__(self):
raise StopIteration
class A(object):
def __iter__(self):
return B()
from collections.abc import *
a = A()
b = B()
print(isinstance(a, Iterable))
print(isinstance(a, Iterator))
print(isinstance(b, Iterable))
print(isinstance(b, Iterator))
結(jié)果是:
True
False
False
False
讓我們稍微對(duì)B這個(gè)類(lèi)做一點(diǎn)修改:
class B(object):
def __next__(self):
raise StopIteration
def __iter__(self):
return None
class A(object):
def __iter__(self):
return B()
from collections.abc import *
a = A()
b = B()
print(isinstance(a, Iterable))
print(isinstance(a, Iterator))
print(isinstance(b, Iterable))
print(isinstance(b, Iterator))
結(jié)果是:
True
False
True
True
真正的迭代器
上面只是做了幾個(gè)演示,這里具體說(shuō)明一下:
當(dāng)調(diào)用iter函數(shù)的時(shí)候,生成了一個(gè)迭代對(duì)象,要求__iter__必須返回一個(gè)實(shí)現(xiàn)了__next__的對(duì)象,我們就可以通過(guò)next函數(shù)訪問(wèn)這個(gè)對(duì)象的下一個(gè)元素了,并且在你不想繼續(xù)有迭代的情況下拋出一個(gè)StopIteration的異常(for語(yǔ)句會(huì)捕獲這個(gè)異常,并且自動(dòng)結(jié)束for),下面實(shí)現(xiàn)了一個(gè)自己的類(lèi)似range函數(shù)的功能。
class MyRange(object):
def __init__(self, end):
self.start = 0
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.start < self.end:
ret = self.start
self.start += 1
return ret
else:
raise StopIteration
from collections.abc import *
a = MyRange(5)
print(isinstance(a, Iterable))
print(isinstance(a, Iterator))
for i in a:
print(i)
結(jié)果是:
True
True
0
1
2
3
4
接下來(lái)我們使用next函數(shù)模擬一次:
class MyRange(object):
def __init__(self, end):
self.start = 0
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.start < self.end:
ret = self.start
self.start += 1
return ret
else:
raise StopIteration
a = MyRange(5)
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a)) # 其實(shí)到這里已經(jīng)完成了,我們?cè)谶\(yùn)行一次查看異常
可以看見(jiàn)一個(gè)很明顯的好處是,每次產(chǎn)生的數(shù)據(jù),是產(chǎn)生一個(gè)用一個(gè),什么意思呢,比如我要遍歷[0, 1, 2, 3.....]一直到10億,如果使用列表的方式,那么是會(huì)全部載入內(nèi)存的,但是如果使用迭代器,可以看見(jiàn),當(dāng)用到了(也就是在調(diào)用了next)才會(huì)產(chǎn)生對(duì)應(yīng)的數(shù)字,這樣就可以節(jié)約內(nèi)存了,這是一種懶惰的加載方式。
總結(jié)
- 可以使用
collection.abs里面的Iterator和Iterable配合isinstance函數(shù)來(lái)判斷一個(gè)對(duì)象是否是可迭代的,是否是迭代器對(duì)象 -
iter實(shí)際是映射到了__iter__函數(shù) - 只要實(shí)現(xiàn)了
__iter__的對(duì)象就是可迭代對(duì)象(Iterable),正常情況下,應(yīng)該返回一個(gè)實(shí)現(xiàn)了__next__的對(duì)象(雖然這個(gè)要求不強(qiáng)制),如果自己實(shí)現(xiàn)了__next__,當(dāng)然也可以返回自己 - 同時(shí)實(shí)現(xiàn)了
__iter__和__next__的是迭代器(Iterator),當(dāng)然也是一個(gè)可迭代對(duì)象了,其中__next__應(yīng)該在迭代完成后,拋出一個(gè)StopIteration異常 -
for語(yǔ)句會(huì)自動(dòng)處理這個(gè)StopIteration異常以便結(jié)束for循環(huán)
生成器相關(guān)的文檔已經(jīng)在這里。