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