疊代器
疊代是通路集合元素的一種方式。疊代器是一個可以記住周遊的位置的對象。疊代器對象從集合的第一個元素開始通路,直到所有的元素被通路完結束。疊代器隻能往前不會後退。
可疊代對象
我們已經知道可以對
list、tuple、str
等類型的資料使用
for...in...
的循環文法從其中依次拿到資料進行使用,我們把這樣的過程稱為周遊,也叫疊代。
但是,是否所有的資料類型都可以放到
for...in...
的語句中,然後讓
for...in...
每次從中取出一條資料供我們使用,即供我們疊代嗎?
>>> for i in 100:
... print(i)
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
>>>
複制
很明顯
int
整型不是
iterable
,即
int
整型是不可以疊代的
# 我們自定義一個容器MyList用來存放資料,可以通過add方法向其中添加資料
>>> class MyList(object):
... def __init__(self):
... self.container = []
... def add(self, item):
... self.container.append(item)
...
>>> mylist = MyList()
>>> mylist.add(1)
>>> mylist.add(2)
>>> mylist.add(3)
>>> for num in mylist:
... print(num)
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'MyList' object is not iterable
>>>
# MyList容器的對象也是不能疊代的
複制
我們自定義了一個容器類型
MyList
,在将一個存放了多個資料的
MyList
對象放到
for...in...
的語句中,發現
for...in...
并不能從中依次取出一條資料傳回給我們,也就說我們随便封裝了一個可以存放多條資料的類型卻并不能被疊代使用。
我們把可以通過
for...in...
這類語句疊代讀取一條資料供我們使用的對象稱之為 可疊代對象(Iterable)。
如何判斷一個對象是否可以疊代
可以使用
isinstance()
判斷一個對象是否是
Iterable
對象:
In [1]: from collections import Iterable
In [2]: isinstance([], Iterable)
Out[2]: True
In [3]: isinstance({}, Iterable)
Out[3]: True
In [4]: isinstance('abc', Iterable)
Out[4]: True
In [5]: isinstance(mylist, Iterable)
Out[5]: False
In [6]: isinstance(100, Iterable)
Out[6]: False
複制
可疊代對象的本質
我們分析對可疊代對象進行疊代使用的過程,發現每疊代一次(即在
for...in...
中每循環一次)都會傳回對象中的下一條資料,一直向後讀取資料直到疊代了所有資料後結束。那麼,在這個過程中就應該有一個 “人” 去記錄每次通路到了第幾條資料,以便每次疊代都可以傳回下一條資料。
我們把這個能幫助我們進行資料疊代的 “人” 稱為 疊代器(Iterator)。
可疊代對象的本質就是可以向我們提供一個這樣的中間“人”即疊代器幫助我們對其進行疊代周遊使用。
可疊代對象通過
__iter__()
方法向我們提供一個疊代器,我們在疊代一個可疊代對象的時候,實際上就是先擷取該對象提供的一個疊代器,然後通過這個疊代器來依次擷取對象中的每一個資料.
那麼也就是說,一個具備了
__iter__()
方法的對象,就是一個可疊代對象。
from collections import Iterable
class MyList(object):
def __init__(self):
self.container = []
def add(self, item):
self.container.append(item)
def __iter__(self):
"""傳回一個疊代器"""
# 我們暫時忽略如何構造一個疊代器對象
pass
mylist = MyList()
isinstance(mylist, Iterable) # True
複制
這回測試發現添加了
__iter__()
方法的
mylist
對象已經是一個可疊代對象了
iter()函數與next()函數
list、tuple
等都是可疊代對象,我們可以通過
iter()
函數擷取這些可疊代對象的疊代器。然後對擷取到的疊代器不斷使用
next()
函數來擷取下一條資料。
iter()
函數實際上就是調用了可疊代對象的
__iter__
方法
我們使用
ipython
來調試一下
In [8]: li = [11, 22, 33, 44, 55]
In [9]: li_iter = iter(li)
In [10]: next(li_iter)
Out[10]: 11
In [11]: next(li_iter)
Out[11]: 22
In [12]: next(li_iter)
Out[12]: 33
In [13]: next(li_iter)
Out[13]: 44
In [14]: next(li_iter)
Out[14]: 55
In [15]: next(li_iter)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-15-ab6a83f394a1> in <module>
----> 1 next(li_iter)
StopIteration:
In [16]:
複制
<font color=red>注意:</font> 當我們已經疊代完最後一個資料之後,再次調用
next()
函數會抛出
StopIteration
的異常,來告訴我們所有資料都已疊代完成,不用再執行
next()
函數了。
如何判斷一個對象是否是疊代器
可以使用
isinstance()
判斷一個對象是否是
Iterator
對象:
In [6]: from collections import Iterator
In [7]: isinstance([], Iterator)
Out[7]: False
In [8]: isinstance(iter([]), Iterator)
Out[8]: True
In [9]: isinstance(iter("abc"), Iterator)
Out[9]: True
複制
疊代器Iterator
通過上面的分析,我們已經知道,疊代器是用來幫助我們記錄每次疊代通路到的位置,當我們對疊代器使用
next()
函數的時候,疊代器會向我們傳回它所記錄位置的下一個位置的資料。實際上,在使用
next()
函數的時候,調用的就是疊代器對象的
__next__
方法(Python3中是對象的
__next__
方法,Python2中是對象的
next()
方法)。是以,我們要想構造一個疊代器,就要實作它的
__next__
方法。但這還不夠,python要求疊代器本身也是可疊代的,是以我們還要為疊代器實作
__iter__
方法,而
__iter__
方法要傳回一個疊代器,疊代器自身正是一個疊代器,是以疊代器的
__iter__
方法傳回自身即可。
一個實作了
__iter__
方法和
__next__
方法的對象,就是疊代器。
class MyList(object):
"""自定義的一個可疊代對象"""
def __init__(self):
self.items = []
def add(self, val):
self.items.append(val)
def __iter__(self):
myiterator = MyIterator(self)
return myiterator
class MyIterator(object):
"""自定義的供上面可疊代對象使用的一個疊代器"""
def __init__(self, mylist):
self.mylist = mylist
# current用來記錄目前通路到的位置
self.current = 0
def __next__(self):
if self.current < len(self.mylist.items):
item = self.mylist.items[self.current]
self.current += 1
return item
else:
raise StopIteration
def __iter__(self):
return self
def main():
mylist = MyList()
mylist.add(1)
mylist.add(2)
mylist.add(3)
mylist.add(4)
mylist.add(5)
for num in mylist:
print(num)
if __name__ == '__main__':
main()
複制
運作結果:
1
2
3
4
5
[Finished in 0.1s]
複制
for...in...循環的本質
for item in Iterable
循環的本質就是先通過
iter()函數
擷取可疊代對象
Iterable
的疊代器,然後對擷取到的疊代器不斷調用
next()
方法來擷取下一個值并将其指派給
item
,當遇到
StopIteration
的異常後循環結束。
是以我們可以根據其原理改成
while
循環周遊
def main():
mylist = MyList()
mylist.add(1)
mylist.add(2)
mylist.add(3)
mylist.add(4)
mylist.add(5)
# for num in mylist:
# print(num)
myli_iter = iter(mylist) # 擷取疊代器對象
while True:
try:
item = next(myli_iter)
print(item)
except StopIteration:
break
if __name__ == '__main__':
main()
複制
疊代器的應用場景
我們發現疊代器最核心的功能就是可以通過
next()函數
的調用來傳回下一個資料值。如果每次傳回的資料值不是在一個已有的資料集合中讀取的,而是通過程式按照一定的規律計算生成的,那麼也就意味着可以不用再依賴一個已有的資料集合,也就是說不用再将所有要疊代的資料都一次性緩存下來供後續依次讀取,這樣可以節省大量的存儲(記憶體)空間。
舉個例子,比如,數學中有個著名的 斐波拉契數列(Fibonacci),數列中第一個數為0,第二個數為1,其後的每一個數都可由前兩個數相加得到:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
複制
現在我們想要通過
for...in...
循環來周遊疊代斐波那契數列中的前n個數。那麼這個斐波那契數列我們就可以用疊代器來實作,每次疊代都通過數學計算來生成下一個數。
class FibIterator(object):
"""斐波那契數列疊代器"""
def __init__(self, n):
"""
:param n: int, 指明生成數列的前n個數
"""
self.n = n
# current用來儲存目前生成到數列中的第幾個數了
self.current = 0
# num1用來儲存前前一個數,初始值為數列中的第一個數0
self.num1 = 0
# num2用來儲存前一個數,初始值為數列中的第二個數1
self.num2 = 1
def __next__(self):
"""被next()函數調用來擷取下一個數"""
if self.current < self.n:
num = self.num1
self.num1, self.num2 = self.num2, self.num1+self.num2
self.current += 1
return num
else:
raise StopIteration
def __iter__(self):
"""疊代器的__iter__傳回自身即可"""
return self
if __name__ == '__main__':
fib = FibIterator(10)
for num in fib:
print(num, end=" ")
複制
并不是隻有for循環能接收可疊代對象
除了
for循環
能接收可疊代對象,
list、tuple、set
等也能接收。
li = list(FibIterator(10))
tp = tuple(FibIterator(10))
s = set(FibIterator(10))
print(li)
print(tp)
print(s)
複制
結果如下:
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
(0, 1, 1, 2, 3, 5, 8, 13, 21, 34)
{0, 1, 2, 3, 34, 5, 8, 13, 21}
複制
注意 set 集合去重了。
公衆号
建立檔案夾X
大自然用數百億年創造出我們現實世界,而程式員用幾百年創造出一個完全不同的虛拟世界。我們用鍵盤敲出一磚一瓦,用大腦建構一切。人們把1000視為權威,我們反其道行之,捍衛1024的地位。我們不是鍵盤俠,我們隻是平凡世界中不凡的締造者 。