知足迭代協議的對象就是迭代器
iterator就是實現了Iteration Protocol的對象,這類對象都支持循環遍歷的操做(for/while/支持迭代的函數list() sum()...)。html
內建函數iter()
接收一個可迭代對象,並返回一個可迭代對象.
每次將這個可迭代對象傳遞給next()
函數,都會返回它所包含的下一個元素,當迭代完最後一個元素時,就會觸發StopIteration異常。python
>>> x = iter([1, 2, 3]) >>> x <listiterator object at 0x1004ca850> >>> next(x) 1 >>> next(x) 2 >>> next(x) 3 >>> next(x) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
知足以上要求的對象,就是迭代器。編程
在每次的迭代語句中,python都會按照迭代協議去對迭代器進行迭代。其實,在實際執行中,python會進行一些其餘的操做:函數
將須要迭代的對象做爲參數傳遞給iter
函數lua
iter
返回一個迭代器對象code
每次循環則將返回的迭代器對象傳遞給next
函數htm
循環至最後一個元素,觸發StopIteration
對象
以for語句爲例:
當咱們在Python中執行循環語句for i in foo
的時候,其背後的操做是:內存
foo = iter(foo)文檔
next(foo)
next(foo)在python3中執行的是:foo.__next__()
,在python2中則是:foo.next()
迭代器是用class來實現的。其中必需實現的有兩個方法:__iter__
、next
(python2)/__next__
(python3)。其中,__iter__
必需返回一個迭代器對象,next
則負責迭代邏輯並在迭代完畢時觸發異常。
以下:
def Iter(object) def __init__(self): pass def __iter__(self): pass def __next__(self): # python3 pass def next(self): # python2 pass
__iter__
返回self
迭代器的__iter__
方法須要返回的是一個具備next
方法的可迭代對象。若是當__iter__
返回的是self
的話,就會產生其餘意想不到的效果。
class yrange: def __init__(self, n): self.i = 0 self.n = n def __iter__(self): return self def next(self): if self.i < self.n: i = self.i self.i += 1 return i else: raise StopIteration() class zrange: def __init__(self, n): self.i = 0 self.n = n def __iter__(self): return zrange(self.n) def next(self): if self.i < self.n: i = self.i self.i += 1 return i else: raise StopIteration()
執行結果:
>>> y = yrange(5) >>> list(y) [0, 1, 2, 3, 4] >>> list(y) [] >>> z = zrange(5) >>> list(z) [0, 1, 2, 3, 4] >>> list(z) [0, 1, 2, 3, 4]
在yrange
中,iter
返回的是self
,在執行list(y)
時iter
返回的都是同一個self
,因此再次調用list(y)
時只會觸發結束迭代異常,列表中並沒有內容。
而在zrange
中,每次執行list(z)
時,iter
都是返回一個新的迭代器zrange(self.n)
,因此每次執行list(z)
都獲得完整的元素。
一般,對於數據量特別大的序列,咱們會用生成器generator
來代替容器對象container
,這樣能夠利用lazy evaluable來節省內存開銷。值得注意的是,生成器也是一個只能迭代一次的迭代器。
def grange(n): i = 0 while i < n: yield i i += 1
執行結果:
>>> glist = grange(10) >>> list(glist) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> list(glist) []
若是是利用便捷的生成器表達式也是同樣:
>>> alist = (i for i in range(10)) >>> list(alist) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> list(alist) []
要解決這個問題,能夠將迭代器和生成器組合使用:
class Grange(object): def __init__(self, n): self.n = n def __iter__(self): for i in range(self.n): yield i
結果:
>>> glist = Grange(10) >>> list(glist) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> list(glist) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
值得注意的是,日常咱們利用到生成器的地方都是數據量特別大的狀況,這個時候,其實應該儘可能避免屢次迭代生成器。我想這應該也是python沒有支持對生成器屢次迭代的特性的緣由。
在實際的編程中,每每須要在函數中屢次迭代一個序列,若是這個序列是調用API獲得的,而你又不能保證它是沒有陷阱的迭代器時。能夠在遍歷迭代器的時候,加入一個判斷語句,避免沒法屢次迭代的狀況發生:
def iterator_checker(iterator): assert iter(iterator) is not iter(iterator), "iter() return self"
<<Effective Python>>