迭代器
迭代(iter)
我們經(jīng)常聽說過"版本迭代"這個詞,意思是在原來版本的基礎上,再提升一個版本的過程。那么我們僅僅看看"迭代"這個詞,會發(fā)現(xiàn)迭代就是一個根據(jù)原來的狀態(tài)決定本次狀態(tài)的過程
迭代應用于Python中,迭代具體是指根據(jù)原來的數(shù)據(jù)輸出(并不一定是要打印,也可能僅僅是取出數(shù)據(jù)),決定本次數(shù)據(jù)輸出的過程
可迭代(iterable)
就可以根據(jù)原來的數(shù)據(jù)輸出(并不一定是要打印,也可能僅僅是取出數(shù)據(jù)),決定本次數(shù)據(jù)輸出,即輸出數(shù)據(jù)時候具有迭代的能力,即為可迭代
可迭代對象
即輸出數(shù)據(jù)時候具有迭代的能力的對象為可迭代對象
迭代器(iterator)
使得可迭代對象具有迭代的能力的對象,也就是每一個可迭代對象就是因為具有一個迭代器,所以才具有迭代的能力,即對應一個可迭代對象來說,必須具有迭代器
本質上每一次對可迭代對象進行迭代操作的時候,可迭代對象都是讓迭代器去執(zhí)行這個迭代操作,即迭代器就是一個打工人,為可迭代對象打工,使得可迭代對象可以進行迭代操作
遍歷
遍歷,這個詞我相信讀者一定不陌生,遍歷就是一個將所有數(shù)據(jù)依次取出的過程,結合"迭代"的概念來看,遍歷中每一次將數(shù)據(jù)取出的操作,都是要先知曉上一次取出的哪一個數(shù)據(jù),然后才能根據(jù)上一次取出的數(shù)據(jù),再取出這一次應該取出的數(shù)據(jù),這就是一個迭代的過程
所以,遍歷就是一個不斷迭代的過程,迭代可以看作是遍歷的一個子過程,可以進行迭代操作是可以進行遍歷的基礎,可以實現(xiàn)的遍歷操作的對象必定是一個可迭代對象
Python中,遍歷操作的方式就是進行for循環(huán)
iter函數(shù)
上面提到可迭代對象必須具有迭代器,否則就不具有迭代的能力,而使用iter函數(shù)可以將一個可迭代對象的迭代器取出(迭代器:“我 free(免費)啦”)
lst = [1, 2, 3]
for num in lst: #對列表進行遍歷,如果可以成功完成,就說明列表類型對象為可迭代對象
print(num)
iterator = iter(lst) #取出列表的迭代器
print(iterator)
#輸出結果:
"""
1
2
3
<list_iterator object at 0x000002A0E654ACD0>
"""
上面也提到過,可迭代對象進行迭代操作的本質就是讓它的迭代器去完成迭代操作,所以可迭代對象可被遍歷,那么我們也可以嘗試一下,對于迭代器本身,是否可遍歷
lst = [1, 2, 3]
iterator = iter(lst)
for num in iterator: #對迭代器進行遍歷
print(num)
#輸出結果:
"""
1
2
3
"""
結果是迭代器對象是可遍歷的
可迭代對象的定義是具有迭代能力的對象,而迭代器現(xiàn)在我們嘗試出來是可以被遍歷,而遍歷的本質就是可迭代,即迭代器是具有迭代能力的,所以,迭代器實際上也是可迭代對象
那既然可迭代對象可以被調用iter函數(shù),那迭代器是否可以調用呢?
lst = [1, 2, 3]
iterator_1 = iter(lst)
iterator_2 = iter(iterator_1) #針對迭代器調用iter函數(shù)
print(iterator_2)
#輸出結果:
"""
<list_iterator object at 0x0000021EED8BACD0>
"""
從結果來看,迭代器是可以調用iter函數(shù)的
next函數(shù)
使用next函數(shù)可以對一個迭代器完成一次迭代操作,即可以根據(jù)上次取出的數(shù)據(jù),決定這一次取出數(shù)據(jù)
lst = [1, 2, 3]
iterator = iter(lst)
print(next(iterator)) #對迭代器進行一次迭代操作
print(next(iterator)) #對迭代器進行一次迭代操作
print(next(iterator)) #對迭代器進行一次迭代操作
#輸出結果:
"""
1
2
3
"""
其實可迭代對象被遍歷的本質就是一次次對其對應的迭代器進行這樣的操作完成的
這個時候就會有讀者會問了,如果再對迭代器調用一次next函數(shù)會咋樣嘞?以及如果內部是通過一次次對迭代器調用next函數(shù),那怎么知道何時結束調用呢?畢竟可迭代對象遍歷的時候,我們看到的現(xiàn)象是正正好把所有的數(shù)據(jù)都取出了,不多一個,也不少一個呀
其實這兩個問題可以一起回答
我們先看看連續(xù)對一個迭代器調用next函數(shù)的次數(shù)超過了可迭代對象的數(shù)據(jù)總數(shù)會怎么樣
lst = [1, 2, 3]
iterator = iter(lst)
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator)) #連續(xù)地,第四次調用next函數(shù)
#引發(fā)異常:沒有任何異常信息,異常類型為StopIteration
引發(fā)了異常,而且必定是第四次調用導致的
這就表明了,對于迭代器的迭代操作,并不是循環(huán)式的(也就是取出最后一個元素后,并不會再從頭開始取數(shù)據(jù)),而且結束的方式是拋出異常,即一個迭代器只能實現(xiàn)一次遍歷,不能二次遍歷(可以猜測,一個可迭代對象是可以實現(xiàn)多次遍歷的,而一個迭代器只能實現(xiàn)一次遍歷,所以,一個可迭代對象可以產生多個迭代器,每一次遍歷過程都要消耗一個迭代器,讀者有興趣的可以自行測試,我這里就不再展示了)
這是對第一個問題的回答
既然看到了這樣的結果,第二個問題也就迎刃而解了,只需要寫一個while死循環(huán),然后添加上異常處理機制,如果出現(xiàn)了異常,表明數(shù)據(jù)全部取出,就可以結束打破循環(huán)出去了,就像下面這個代碼一樣
lst = [1, 2, 3]
iterator = iter(lst)
print('對迭代器的遍歷開始')
while True:
try:
print(next(iterator))
except StopIteration as ret:
break
print('對迭代器的遍歷結束')
#輸出結果:
"""
對迭代器的遍歷開始
1
2
3
對迭代器的遍歷結束
"""
可以看到完美實現(xiàn)了對于一個迭代器的遍歷過程,這也就是對一個可迭代對象的遍歷過程,其實這也就是for循環(huán)的本質,不過具體的驗證要在實現(xiàn)自定義可迭代對象與自定義迭代器后才可以實現(xiàn)
自定義可迭代對象與自定義迭代器
上面提到過,可迭代對象就是具有迭代能力的對象,而迭代能力指的是根據(jù)上一次的取出的數(shù)據(jù),決定這一次應該取出的數(shù)據(jù)。迭代器就是可迭代對象具有迭代能力的本質,即迭代工作實際上是由迭代器完成的,遍歷也就是進行了多次迭代操作,于是可迭代對象的遍歷實際上也是迭代器負責的
想要使用關鍵字class去自定義可迭代對象和迭代器,就必須深刻了解可迭代對象的本質和迭代器的本質,就必須要使用一個方法來準確判斷自己定義的這個對象,是否為一個可迭代對象,或者是一個迭代器
- 針對于可迭代對象與迭代器的判斷,提供一個途徑
- 可以導入collections.abc模塊中的Iterable和Iterator,配合函數(shù)isinstance,實現(xiàn)準確判斷
對于可迭代對象與迭代器判斷工具的題外話(不重要)
可能在一些地方,讀者會看到是從collections模塊導入Iterable和Iterator的,但是建議寫from collections.abc import Iterable,因為from collections import Iterable在python 3.8及更高級版本停止使用,如果使用會拋出這樣的異常信息:
DeprecationWarning: Using or importing the ABCs from ‘collections’ instead of from ‘collections.abc’ is deprecated, and in 3.8 it will stop working from collections import Iterable
from collections.abc import Iterator, Iterable
lst = [1, 2, 3]
iterator = iter(lst)
print(isinstance(lst, Iterable)) #判斷列表對象是否為可迭代對象
print(isinstance(iterator, Iterator)) #判斷列表的迭代器是否為迭代器
print(isinstance(iterator, Iterable)) #判斷列表的迭代器是否為可迭代對象
#輸出結果:
"""
True
True
True
"""
- 針對于可迭代對象與迭代器的構造,提供兩個方法
- __iter__和__next__
單單從方法名來看,就很容易讓讀者聯(lián)想到iter函數(shù)和next函數(shù)
它們的聯(lián)系請看下面兩個案例
class C:
def __iter__(self):
print('__iter__方法被調用')
return 10
ins = C()
iter(ins)
#輸出結果:__iter__方法被調用
#打印上面的結果以后,引發(fā)異常:iter() returned non-iterator of type 'int'
我們可以看出,__iter__方法是在對一個實例對象調用iter函數(shù)的時候觸發(fā)的
根據(jù)異常信息(即iter函數(shù)的返回值不是一個迭代器,而是一個int類型對象,這個int類型對象就是那個10),以及上面我們對于iter函數(shù)的使用,我們可以斷定,iter函數(shù)的返回值必須是一個迭代器,否則就會像上面這樣拋出異常,以及之前我們反復使用iter函數(shù),得到一個迭代器,實際上就是可迭代對象自動調用了這個__iter__方法,然后這個方法返回一個迭代器實現(xiàn)的
class C:
def __next__(self):
print('__next__方法被調用')
return 10
ins = C()
data = next(ins)
print(data)
#輸出結果:
"""
__next__方法被調用
10
"""
我們可以看出,和上面iter函數(shù)和__iter__的關系類似,所謂調用next函數(shù)實際上就是自動調用__next__方法實現(xiàn)的,而且__next__方法就是next函數(shù)的返回值
所以我們可以斷定,next函數(shù)對迭代器進行一次迭代操作,必定是通過調用迭代器的__next__方法,然后該方法返回一個數(shù)據(jù)實現(xiàn)的
現(xiàn)在我們已經(jīng)具有了判斷一個對象是否為可迭代對象,是否為迭代器的工具了,并且我們知道了調用next函數(shù)和iter函數(shù)的本質,我們就可以聊一聊如何構造一個可迭代對象和迭代器對象了
首先我們先嘗試構造一個基本的可迭代對象和迭代器對象,先不管內部的代碼,即至少讓判斷工具可以認定我們構造出的就是可迭代對象和迭代器對象
一個可迭代對象,我們前面對其調用過iter函數(shù),而iter函數(shù)的本質就是去自動調用__iter__方法,于是我們可以先這樣構造
from collections.abc import Iterator, Iterable
class C:
def __iter__(self):
pass
ins = C()
print(isinstance(ins, Iterable)) #判斷對象是否為可迭代對象
print(isinstance(ins, Iterator)) #判斷對象是否為迭代器
#輸出結果:
"""
True
False
"""
從輸出結果可以看出,具有__iter__方法的對象就是一個可迭代對象,但不是一個迭代器
一個迭代器,我們前面對其調用過iter函數(shù)和next函數(shù),而iter函數(shù)的本質就是去自動調用__iter__方法,next函數(shù)的本質就是去自動調用__next__方法,于是我們可以先這樣構造
from collections.abc import Iterator, Iterable
class C:
def __iter__(self):
pass
def __next__(self):
pass
ins = C()
print(isinstance(ins, Iterable)) #判斷對象是否為可迭代對象
print(isinstance(ins, Iterator)) #判斷對象是否為迭代器
#輸出結果:
"""
True
True
"""
從輸出結果可以看出,同時具有__iter__方法和__next__方法的對象就是一個迭代器,而且是一個可迭代對象
綜上所述:
- iter函數(shù)的本質就是調用對象中的__iter__方法,next函數(shù)的本質就是調用對象中的__next__方法
- 迭代器的本質就是一個同時具有__iter__方法和__next__方法的對象,而可迭代對象的本質就是一個具有__iter__方法的對象,并且可迭代對象和迭代器的關系為:可迭代對象不一定為一個迭代器,而迭代器必定是一個可迭代對象
- 現(xiàn)在除了對于迭代器和可迭代對象可以使用collections.abc模塊中的Iterable和Iterator,配合函數(shù)isinstance,實現(xiàn)準確判斷,也可以直接查看該對象是否具有__iter__方法和__next__方法,從而實現(xiàn)準確判斷
進一步地,可以進行可迭代對象和迭代器的代碼完善了
這里先寫一個可以實現(xiàn)基本功能可迭代對象,以及一個不太完善的迭代器(這個對象其實都不能稱為迭代器,因為其只有__next__方法)
class my_iterable:
def __init__(self):
self.lst = list()
def add(self, data): #實現(xiàn)數(shù)據(jù)的添加
self.lst.append(data)
def __iter__(self): #返回一個'迭代器'對象
return my_iterator(self.lst)
class my_iterator:
def __init__(self, lst):
self.lst = lst
self.count = 0
def __next__(self):
data = self.lst[self.count]
self.count += 1
return data
Iterable = my_iterable() #創(chuàng)建一個可迭代對象
Iterable.add(1) #向該可迭代對象中添加數(shù)據(jù)
Iterable.add(2)
Iterable.add(3)
Iterator = iter(Iterable) #取出可迭代對象的迭代器
print(next(Iterator)) #對迭代器進行進行迭代操作
print(next(Iterator))
print(next(Iterator))
print(next(Iterator))
#輸出結果:
"""
1
2
3
"""
#打印完上面的結果以后,拋出異常:list index out of range
基本是實現(xiàn)了作為迭代器和可迭代對象的基本功能了,但是還是有一些問題,比如最后的異常拋出的異常類型不對,以及迭代器的部分的代碼需要完善一下
在完善代碼之前,先看這兩個代碼
lst = [1,2,3]
lst_iterator_1 = iter(lst) #取出迭代器
print(lst_iterator_1)
print(next(lst_iterator_1)) #對迭代器進行一次迭代操作
lst_iterator_2 = iter(lst) #針對迭代器取出迭代器
print(lst_iterator_2)
print(next(lst_iterator_2)) #對迭代器進行一次迭代操作
#輸出結果:
"""
<list_iterator object at 0x0000015D14C0ACD0>
1
<list_iterator object at 0x0000015D14C0AAC0>
1
"""
從結果可以看出,每一次對可迭代對象調用iter函數(shù),取出的迭代器是不一樣的,即創(chuàng)建了新的迭代器
lst = [1,2,3]
lst_iterator_1 = iter(lst) #取出迭代器
print(lst_iterator_1)
print(next(lst_iterator_1)) #對迭代器進行一次迭代操作
lst_iterator_2 = iter(lst_iterator_1) #針對迭代器取出迭代器
print(lst_iterator_2)
print(next(lst_iterator_2)) #對迭代器進行一次迭代操作
#輸出結果:
"""
<list_iterator object at 0x0000022D1CD5ACD0>
1
<list_iterator object at 0x0000022D1CD5ACD0>
2
"""
從結果可以看出,如果對一個迭代器調用iter函數(shù),取出的迭代器就是這個迭代器本身,即并沒有創(chuàng)建一個新的迭代器
根據(jù)這兩個代碼的運行結果,我們就可以更加真實地去還原迭代器了
class my_iterable:
def __init__(self):
self.lst = list()
def add(self, data): #實現(xiàn)數(shù)據(jù)的添加
self.lst.append(data)
def __iter__(self): #返回一個迭代器對象(創(chuàng)建一個新的迭代器返回)
return my_iterator(self.lst)
class my_iterator:
def __init__(self, lst):
self.lst = lst
self.count = 0
def __iter__(self): #返回一個迭代器對象(即返回自身)
return self
def __next__(self):
if self.count < len(self.lst):
data = self.lst[self.count]
self.count += 1
return data
else: #如果下標超出范圍,拋出StopIteration異常
raise StopIteration
Iterable = my_iterable() #創(chuàng)建一個可迭代對象
Iterable.add(1) #向該可迭代對象中添加數(shù)據(jù)
Iterable.add(2)
Iterable.add(3)
Iterator_1 = iter(Iterable) #取出可迭代對象的迭代器
print(next(Iterator_1)) #對迭代器進行進行迭代操作
print(next(Iterator_1))
Iterator_2 = iter(Iterator_1) #取出迭代器的迭代器
print(next(Iterator_2)) #對迭代器進行進行迭代操作
print(next(Iterator_2))
#輸出結果:
"""
1
2
3
"""
#打印上面的這些信息后引發(fā)異常:沒有任何異常信息,異常類型為StopIteration
Python for循環(huán)的運行過程
其實我們上面構造的迭代器和可迭代對象是可以使用for循環(huán)進行遍歷的
class my_iterable:
def __init__(self):
self.lst = list()
def add(self, data): #實現(xiàn)數(shù)據(jù)的添加
self.lst.append(data)
def __iter__(self): #返回一個迭代器對象(創(chuàng)建一個新的迭代器返回)
return my_iterator(self.lst)
class my_iterator:
def __init__(self, lst):
self.lst = lst
self.count = 0
def __iter__(self): #返回一個迭代器對象(即返回自身)
return self
def __next__(self):
if self.count < len(self.lst):
data = self.lst[self.count]
self.count += 1
return data
else: #如果下標超出范圍,拋出StopIteration異常
raise StopIteration
Iterable = my_iterable()
Iterable.add(1)
Iterable.add(2)
Iterable.add(3)
for num in Iterable: #使用for循環(huán)遍歷自定義的可迭代對象
print(num)
#輸出結果:
"""
1
2
3
"""
現(xiàn)在,我們可以解析一下for循環(huán)的運行過程
from time import sleep
class my_iterable:
def __init__(self):
self.lst = list()
def add(self, data): #實現(xiàn)數(shù)據(jù)的添加
self.lst.append(data)
def __iter__(self): #返回一個迭代器對象(創(chuàng)建一個新的迭代器返回)
print('my_iterable的__iter__被調用')
return my_iterator(self.lst)
class my_iterator:
def __init__(self, lst):
print('my_iterator的__init__被調用')
self.lst = lst
self.count = 0
def __iter__(self): #返回一個迭代器對象(即返回自身)
print('my_iterator的__iter__被調用')
return self
def __next__(self):
print('my_iterator的__next__被調用')
if self.count < len(self.lst):
data = self.lst[self.count]
self.count += 1
return data
else:
print('遍歷結束') #使用print標識程序運行的進程
sleep(2) #使用減緩打印速度
Iterable = my_iterable()
Iterable.add(1)
Iterable.add(2)
Iterable.add(3)
for num in Iterable:
print(num)
#輸出結果:
"""
my_iterable的__iter__被調用
my_iterator的__init__被調用
my_iterator的__next__被調用
1
my_iterator的__next__被調用
2
my_iterator的__next__被調用
3
my_iterator的__next__被調用
遍歷結束
None
"""
#打印完上面的結果以后,不斷打印,程序不會停止
"""
my_iterator的__next__被調用
遍歷結束
None
"""
從上面代碼的運行結果可以知道
- for循環(huán)內部首先會提取可迭代對象的迭代器(通過調用iter函數(shù),觸發(fā)__iter__方法的自動調用)
- 接著for循環(huán)內部會對這個迭代器不斷(這個不斷是用死循環(huán)實現(xiàn)的)進行迭代操作,取出數(shù)據(jù)(通過調用next函數(shù),觸發(fā)__next__方法的自動調用)
- 最后for循環(huán)內部停止對這個迭代器進行迭代操作,是通過捕獲StopIteration異常實現(xiàn)的(讀者可以嘗試將拋出異常的類型進行改變,會發(fā)現(xiàn)程序最后會拋出那個異常,說明for循環(huán)內部僅僅捕獲StopIteration異常,其他類型的異常是不會被捕獲的)
即上面for循環(huán)的等效代碼為
Iterator = iter(Iterable)
while True:
try:
num = next(Iterator)
print(num)
except StopIteration as ret:
print(ret)
實際上除了for循環(huán),在Python的很多地方,底層都是迭代器
比如list函數(shù),tuple函數(shù),dict函數(shù),set函數(shù)的實現(xiàn)實際上也是調用了iter函數(shù)和next函數(shù),進行遍歷,然后轉化為對應類型的序列對象,這里演示一下set函數(shù)
class my_iterable:
def __init__(self):
self.lst = list()
def add(self, data): #實現(xiàn)數(shù)據(jù)的添加
self.lst.append(data)
def __iter__(self): #返回一個迭代器對象(創(chuàng)建一個新的迭代器返回)
print('my_iterable的__iter__被調用')
return my_iterator(self.lst)
class my_iterator:
def __init__(self, lst):
print('my_iterator的__init__被調用')
self.lst = lst
self.count = 0
def __iter__(self): #返回一個迭代器對象(即返回自身)
print('my_iterator的__iter__被調用')
return self
def __next__(self):
print('my_iterator的__next__被調用')
if self.count < len(self.lst):
data = self.lst[self.count]
self.count += 1
return data
else: #如果下標超出范圍,拋出StopIteration異常
raise StopIteration
Iterable = my_iterable()
Iterable.add(1)
Iterable.add(2)
Iterable.add(1)
st = set(Iterable)
print(st)
print(type(st))
#輸出結果:
"""
my_iterable的__iter__被調用
my_iterator的__init__被調用
my_iterator的__next__被調用
my_iterator的__next__被調用
my_iterator的__next__被調用
my_iterator的__next__被調用
{1, 2}
<class 'set'>
"""
可以看到set函數(shù)是先進行遍歷,然后將取出的相同元素僅保留一個,最后得到集合類型對象
包括推導式中的for循環(huán)也是這個底層也是這個機制文章來源:http://www.zghlxwxcb.cn/news/detail-766130.html
class my_iterable:
def __init__(self):
self.lst = list()
def add(self, data): #實現(xiàn)數(shù)據(jù)的添加
self.lst.append(data)
def __iter__(self): #返回一個迭代器對象(創(chuàng)建一個新的迭代器返回)
print('my_iterable的__iter__被調用')
return my_iterator(self.lst)
class my_iterator:
def __init__(self, lst):
print('my_iterator的__init__被調用')
self.lst = lst
self.count = 0
def __iter__(self): #返回一個迭代器對象(即返回自身)
print('my_iterator的__iter__被調用')
return self
def __next__(self):
print('my_iterator的__next__被調用')
if self.count < len(self.lst):
data = self.lst[self.count]
self.count += 1
return data
else: #如果下標超出范圍,拋出StopIteration異常
raise StopIteration
Iterable = my_iterable()
Iterable.add(1)
Iterable.add(2)
Iterable.add(3)
lst = [i for i in Iterable]
print(lst)
#輸出結果:
"""
my_iterable的__iter__被調用
my_iterator的__init__被調用
my_iterator的__next__被調用
my_iterator的__next__被調用
my_iterator的__next__被調用
my_iterator的__next__被調用
[1, 2, 3]
"""
序列解包也是這個機制文章來源地址http://www.zghlxwxcb.cn/news/detail-766130.html
class my_iterable:
def __init__(self):
self.lst = list()
def add(self, data): #實現(xiàn)數(shù)據(jù)的添加
self.lst.append(data)
def __iter__(self): #返回一個迭代器對象(創(chuàng)建一個新的迭代器返回)
print('my_iterable的__iter__被調用')
return my_iterator(self.lst)
class my_iterator:
def __init__(self, lst):
print('my_iterator的__init__被調用')
self.lst = lst
self.count = 0
def __iter__(self): #返回一個迭代器對象(即返回自身)
print('my_iterator的__iter__被調用')
return self
def __next__(self):
print('my_iterator的__next__被調用')
if self.count < len(self.lst):
data = self.lst[self.count]
self.count += 1
return data
else: #如果下標超出范圍,拋出StopIteration異常
raise StopIteration
Iterable = my_iterable()
Iterable.add(1)
Iterable.add(2)
Iterable.add(3)
a, b, c = Iterable
print(a, b, c)
到了這里,關于Python——迭代器(可迭代、可迭代對象、迭代器、遍歷本質、iter函數(shù)、next函數(shù)、__iter__方法、__next__方法、自定義可迭代對象與自定義迭代器、for循環(huán)本質)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!