python迭代器資料整理

什麼是迭代器

相關概念定義

迭代器(Iterator):

知足迭代協議的對象就是迭代器
iterator就是實現了Iteration Protocol的對象,這類對象都支持循環遍歷的操做(for/while/支持迭代的函數list() sum()...)。html

迭代協議(Iteration Protocol):

內建函數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會進行一些其餘的操做:函數

  1. 將須要迭代的對象做爲參數傳遞給iter函數lua

  2. iter返回一個迭代器對象code

  3. 每次循環則將返回的迭代器對象傳遞給next函數htm

  4. 循環至最後一個元素,觸發StopIteration對象

for語句爲例
當咱們在Python中執行循環語句for i in foo的時候,其背後的操做是:內存

  1. foo = iter(foo)文檔

  2. 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"

參考資料

相關文章
相關標籤/搜索