迭代器
迭代器是在python2.2中被加入的,它為類序列對象提供了一個類序列的接口。有了迭代器可以迭代一個不是序列的對象,因為他表現出了序列的行為。當在python中使用for循環迭代一個對象時,調用者幾乎分辨不出他迭代的是一個迭代器對象還是一個序列對象,因為python讓他(迭代器)像一個序列那樣操作。
如何迭代
本質上說迭代器是個對象,但是這個對象有個特殊的方法next()(在python3中使用__next__()代替了next方法)。當使用for循環來遍歷整個對象時候,就會自動調用此對象的__next__()方法並獲取下一個item。當所有的item全部取出后就會拋出一個StopIteration異常,這並不是錯誤的發生,而是告訴外部調用者迭代完成了,外部的調用者嘗試去捕獲這個異常去做進一步的處理。
不過迭代器是有限制的,例如
不能向后移動
不能回到開始
也無法復制一個迭代器。
因此要再次進行迭代只能重新生成一個新的迭代器對象。
獲取迭代器
對於python內置的可迭代(iterable)對象,可以通過內置的iter()函數來獲取相應的迭代器對象。
Python
a = [1,2,3,45]
type(a)
list
a = iter(a)
type(a)
list_iterator
1
2
3
4
5
6
7
8
9
a = [1,2,3,45]
type(a)
list
a = iter(a)
type(a)
list_iterator
這樣就獲取了list相應的迭代器對象。
我們來看一下該迭代器對象的屬性:
Python
dir(a)
['__class__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__iter__',
'__le__',
'__length_hint__',
'__lt__',
'__ne__',
'__new__',
'__next__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__setstate__',
'__sizeof__',
'__str__',
'__subclasshook__']
可見此迭代對象具有兩個特殊的成員方法__iter__()和__next__(),這兩個方法便是支持迭代器協議所需要實現的方法。其中__iter__()方法返回迭代器對象本身,__next__()方法返回容器的下一個元素,直到結尾拋出StopIteration異常。
我們來測試一下這個list_iterator對象的這兩個方法:
__iter__()返回的對象就是迭代器對象本身。
Python
a = [1,2,3,45]
a = iter(a)
a.__iter__()
<list_iterator at 0x3a33f10>a
<list_iterator at 0x3a33f10>a is a.__iter__()
True
In [1]: a = [1,2,3,45]
In [2]: a = iter(a)
In [3]: a.__iter__()
Out[3]: <list_iterator at 0x3a33f10>
In [4]: a
Out[4]: <list_iterator at 0x3a33f10>
In [5]: a is a.__iter__()
Out[5]: True
In [6]:
__next__()方法返回容器中的值直到結尾。
Python
In [6]: a.__next__()
Out[6]: 1
In [7]: a.__next__()
Out[7]: 2
In [8]: a.__next__()
Out[8]: 3
In [9]: a.__next__()
Out[9]: 45
In [10]: a.__next__()
------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-10-73aa2c76d676> in <module>()
----> 1 a.__next__()
In [6]: a.__next__()
Out[6]: 1
In [7]: a.__next__()
Out[7]: 2
In [8]: a.__next__()
Out[8]: 3
In [9]: a.__next__()
Out[9]: 45
In [10]: a.__next__()
------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-10-73aa2c76d676> in <module>()
----> 1 a.__next__()
StopIteration:
In [11]:
2. 創建迭代器對象
除了使用iter()函數將內置的序列對象轉換成相應的迭代器,我們可以自己實現迭代器協議創建迭代器對象,要實現迭代器協議也就是要在類中實現__iter__()和__next__()方法。
下面我寫一個與list_iterator相同行為的迭代器:
Python
class ListIter(object):
def __init__(self, data):
self.__data = data
self.__count = 0
def __iter__(self):
return self
def __next__(self):
if self.__count < len(self.__data):
val = self.__data[self.__count]
self.__count += 1
return val
else:
raise StopIteration
class ListIter(object):
def __init__(self, data):
self.__data = data
self.__count = 0
def __iter__(self):
return self
def __next__(self):
if self.__count < len(self.__data):
val = self.__data[self.__count]
self.__count += 1
return val
else:
raise StopIteration
我們就可以使用for循環來遍歷這個迭代器了:
Python
In [16]: a = ListIter([1,2,3,4,5])
In [17]: for i in a:
....: print(i)
....:
In [16]: a = ListIter([1,2,3,4,5])
In [17]: for i in a:
....: print(i)
....:
對於迭代器對象,使用for循環遍歷整個數組其實是個語法糖,他的內部實現還是通過調用對象的__next__()方法。
實際上他內部的工作原理應該是這樣的:
a = ListIter([1, 2, 3, 4, 5])
while True:
try:
i = a.__next__()
except StopIteration:
break
// do something in for loop
print(i)
a = ListIter([1, 2, 3, 4, 5])
while True:
try:
i = a.__next__()
except StopIteration:
break
// do something in for loop
print(i)
迭代器支持多次迭代
正如前面所說的迭代器對象不支持重新迭代,也就是同一個迭代器對象無法多次迭代,如:
Python
In [19]: a = ListIter([1,2,3,4,5])
In [20]: [i for i in a]
Out[20]: [1, 2, 3, 4, 5]
In [21]: [i for i in a]
Out[21]: []
In [19]: a = ListIter([1,2,3,4,5])
In [20]: [i for i in a]
Out[20]: [1, 2, 3, 4, 5]
In [21]: [i for i in a]
Out[21]: []
In [22]:
可見,當我再次迭代迭代器a的時候便只返回了空列表,這是因為for循環直接捕獲了StopIteration異常。如果要再次迭代生成列表的話只能重新生成一個新的迭代器對象。
為了能夠解決這個問題,可以分別定義一個可迭代對象(iterables)和迭代器對象(iterator).
插入小插曲:
對於可迭代對象和迭代器對象,我的理解是:
可迭代對象是實現了__iter__()方法的對象,__iter__()可以返回一個迭代器對象。
迭代器對象是實現了__next__()方法的對象,其中他的__iter__()返回的是迭代器對象本身。
我把代碼做了修改,如下:
Python
class ListIterable(object):
def __init__(self, data):
self.__data = data
def __iter__(self):
print("call iterable __iter__().")
return ListIterator(self.__data)
class ListIterator(object):
def __init__(self, data):
self.__data = data
self.__count = 0
def __iter__(self):
print("call iterator __iter__().")
return self
def __next__(self):
print("call iterator __next__().")
if self.__count < len(self.__data):
val = self.__data[self.__count]
self.__count += 1
return val
else:
raise StopIteration
class ListIterable(object):
def __init__(self, data):
self.__data = data
def __iter__(self):
print("call iterable __iter__().")
return ListIterator(self.__data)
class ListIterator(object):
def __init__(self, data):
self.__data = data
self.__count = 0
def __iter__(self):
print("call iterator __iter__().")
return self
def __next__(self):
print("call iterator __next__().")
if self.__count < len(self.__data):
val = self.__data[self.__count]
self.__count += 1
return val
else:
raise StopIteration
為了知道python何時調用__iter__()方法,我添加了一個printf函數來做標記。
現在把這兩個類導入到當前空間中:
Python
In [1]: from list_iter import *
In [2]: a = ListIterable([1,2,4,5,6])
In [3]: b = a.__iter__()
call iterables __iter__().
In [4]: a
Out[4]: <list_iter.ListIterable at 0x39446d0>
In [5]: b
Out[5]: <list_iter.ListIterator at 0x39447b0>
In [1]: from list_iter import *
In [2]: a = ListIterable([1,2,4,5,6])
In [3]: b = a.__iter__()
call iterables __iter__().
In [4]: a
Out[4]: <list_iter.ListIterable at 0x39446d0>
In [5]: b
Out[5]: <list_iter.ListIterator at 0x39447b0>
In [6]:
可見a是iterable對象(實現了__iter__()),b是iterator對象(實現了__next__())。
下面看看這樣做是不是就可以重復多次迭代了:
Python
In [6]: [i for i in a]
call iterable __iter__().
call iterator __next__().
call iterator __next__().
call iterator __next__().
call iterator __next__().
call iterator __next__().
call iterator __next__().
Out[6]: [1, 2, 4, 5, 6]
In [7]: [i for i in a]
call iterable __iter__().
call iterator __next__().
call iterator __next__().
call iterator __next__().
call iterator __next__().
call iterator __next__().
call iterator __next__().
Out[7]: [1, 2, 4, 5, 6]
In [6]: [i for i in a]
call iterable __iter__().
call iterator __next__().
call iterator __next__().
call iterator __next__().
call iterator __next__().
call iterator __next__().
call iterator __next__().
Out[6]: [1, 2, 4, 5, 6]
In [7]: [i for i in a]
call iterable __iter__().
call iterator __next__().
call iterator __next__().
call iterator __next__().
call iterator __next__().
call iterator __next__().
call iterator __next__().
Out[7]: [1, 2, 4, 5, 6]
In [8]:
重復迭代是可以了,從輸出中我們可以看出一些什么來
我們在使用迭代工具對iterable對象進行迭代的時候首先調用的是iterable的__iter__()方法,返回一個迭代器對象,也就是ListIterator的實例。
然后再遍歷的時候是調用iterator的next方法輸出值。
這樣就可以解釋了為什么這樣處理能夠多次迭代了,因為每次使用迭代工具迭代的時候都會調用__iter__()返回一個新的迭代器對象,這樣就相當於創建多個迭代器了,自然可以看起來是重復迭代了!
可變對象和迭代器
在迭代可變對象時候,一個序列的迭代器只是記錄當前到達了序列中的第幾個元素,所以如果在迭代過程中改變了序列的元素。更新會立即反應到所迭代的條目上。
我寫了個測試看了下,的確:
Python
In [13]: c = [1,2,3,4,5]
In [14]: d = iter(c)
In [15]: for i in c:
....: print(i)
....: c.remove(i)
....:
In [13]: c = [1,2,3,4,5]
In [14]: d = iter(c)
In [15]: for i in c:
....: print(i)
....: c.remove(i)
....:
可見上面邊迭代邊刪除列表的元素,但是最后卻只輸出了1, 3, 5,這是為啥?
既然迭代器只記得是在列表中第幾個元素,那么當在第0個元素的時候將會輸出1然后刪除1,這是列表變成了
Python
[2, 3, 4, 5]
1
[2, 3, 4, 5]
但是迭代器記得我是在第二個位置上面,就指向了列表中的第二個位置上,也就是3,然后輸出3.
以此類推,最后只能輸出1,3,5了。
如果我猜測的沒錯的話,剩余的列表應該只剩下2和4了:
Python
In [17]: c
Out[17]: [2, 4]
1
2
In [17]: c
Out[17]: [2, 4]