首先,廖雪峰老師的教程中解釋了迭代器和生成器,這篇文章只是補充和我個人的總結。
什麼是迭代
可以直接作用於for迴圈的物件統稱為可迭代物件(Iterable)。
可以被next()函式呼叫並不斷返回下一個值的物件稱為迭代器(Iterator)。
所有的Iterable均可以通過內建函式iter()來轉變為Iterator。
對迭代器來講,有一個__next()就夠了。在你使用for 和 in 語句時,程式就會自動呼叫即將被處理的物件的迭代器物件,然後使用它的next__()方法,直到監測到一個StopIteration異常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
>>> L = [1,2,3] >>> [x**2 for x in L] [1, 4, 9] >>> next(L) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'list' object is not an iterator >>> I=iter(L) >>> next(I) 1 >>> next(I) 2 >>> next(I) 3 >>> next(I) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration |
上面例子中,列表L可以被for進行迴圈但是不能被內建函式next()用來查詢下一個值,所以L是Iterable。
L通過iter進行包裝後設為I,I可以被next()用來查詢下一個值,所以I是Iterator。
題外話:
- 內建函式iter()僅僅是呼叫了物件的__iter()方法,所以list物件內部一定存在方法iter__()
- 內建函式next()僅僅是呼叫了物件的__next()方法,所以list物件內部一定不存在方法next__(),但是Itrator中一定存在這個方法。
- for迴圈內部事實上就是先呼叫iter()把Iterable變成Iterator在進行迴圈迭代的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
>>> L = [4,5,6] >>> I = L.__iter__() >>> L.__next__() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'list' object has no attribute '__next__' >>> I.__next__() 4 >>> from collections import Iterator, Iterable >>> isinstance(L, Iterable) True >>> isinstance(L, Iterator) False >>> isinstance(I, Iterable) True >>> isinstance(I, Iterator) True >>> [x**2 for x in I] [25, 36] |
4.Iterator繼承自Iterable,從下面的測試中可以很方便的看到Iterator包含__iter()和next()方法,而Iteratble僅僅包含iter__()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
>>> from collections import Iterator, Iterable >>> help(Iterator) Help on class Iterator: class Iterator(Iterable) | Method resolution order: | Iterator | Iterable | builtins.object |**註解:從這裡可以看出Iterable繼承自object, Iterator繼承自Iterable。 | Methods defined here: | | __iter__(self) | | __next__(self) | Return the next item from the iterator. When exhausted, raise StopIteration ...... >>> help(Iterable) Help on class Iterable: class Iterable(builtins.object) | Methods defined here: | | __iter__(self) ...... |
iterable需要包含有__iter()方法用來返回iterator,而iterator需要包含有next__()方法用來被迴圈
如果我們自己定義迭代器,只要在類裡面定義一個 iter() 函式,用它來返回一個帶 next() 方法的物件就夠了。
直接上程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Iterable: def __iter__(self): return Iterator() class Iterator: def __init__(self): self.start=-1 def __next__(self): self.start +=2 if self.start >10: raise StopIteration return self.start I = Iterable() for i in I: print(i) |
上面的程式碼實現的是找到10以內的奇數,程式碼中的類名可以隨便取,不是一定需要使用我上面提供的類名的。
如果在Iterator的__next__方法中沒有實現StopIteration異常,那麼則是表示的全部奇數,那麼需要在呼叫的時候設定退出迴圈的條件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Iterable: def __iter__(self): return Iterator() class Iterator: def __init__(self): self.start=-1 def __next__(self): self.start +=2 return self.start I = Iterable() for count, i in zip(range(5),I): #也可以用內建函式enumerate來實現計數工作。 print(i) |
我們通過range來實現列印多少個元素,這裡表示列印5個元素,返回結果和上面一致。
當然,我們可以把這兩個類合併在一起,這樣實現程式的簡練。
最終版本如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Iterable: def __iter__(self): return self def __init__(self): self.start=-1 def __next__(self): self.start +=2 if self.start >10: raise StopIteration return self.start I = Iterable() for i in I: print(i) |
複製迭代器
迭代器是一次性消耗品,使用完了以後就空了,請看。
1 2 3 4 5 6 7 8 9 10 |
>>> L=[1,2,3] >>> I=iter(L) >>> for i in I: ... print(i, end='-') ... 1-2-3- >>>next(I) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration |
當迴圈以後就殆盡了,再次使用呼叫時會引發StopIteration異常。
我們想通過直接賦值的形式把迭代器儲存起來,可以下次使用。
但是通過下面的範例可以看出來,根本不管用。
1 2 3 4 5 6 7 8 9 10 11 12 |
>>> I=iter(L) >>> J=I >>> next(I) 1 >>> next(J) 2 >>> next(I) 3 >>> next(J) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration |
那怎麼樣才能達到我們要的效果呢?
我們需要使用copy包中的deepcopy了,請看下面:
1 2 3 4 5 6 7 8 9 |
>>> import copy >>> I=iter(L) >>> J=copy.deepcopy(I) >>> next(I) 1 >>> next(I) 2 >>> next(J) 1 |
補充:迭代器不能向後移動, 不能回到開始。
所以需要做一些特殊的事情才能實現向後移動等功能。
以上程式碼均在Python 3.4 中測試通過。
日誌:
- 8月13日完成
- 8月14日新增關於Iterator, Iterable的更多解釋在題外話的第4點。