python迭代器與產生器詳解

來源:互聯網
上載者:User
例子

老規矩,先上一個代碼:

def add(s, x): return s + xdef gen(): for i in range(4):  yield ibase = gen()for n in [1, 10]: base = (add(i, n) for i in base)print list(base)

這個東西輸出可以腦補一下, 結果是[20,21,22,23], 而不是[10, 11, 12, 13]。 當時糾結了半天,一直沒搞懂,後來齊老師稍微指點了一下, 突然想明白了--真夠笨的,唉。。好了--正好趁機會稍微小結一下python裡面的產生器。

迭代器(iterator)

要說產生器,必須首先說迭代器
區分iterable,iterator與itertion
講到迭代器,就需要區別幾個概念:iterable,iterator,itertion, 看著都差不多,其實不然。下面區分一下。

itertion: 就是迭代,一個接一個(one after another),是一個通用的概念,比如一個迴圈遍曆某個數組。
iterable: 這個是可迭代對象,屬於python的名詞,範圍也很廣,可重複迭代,滿足如下其中之一的都是iterable:
可以for迴圈: for i in iterable
可以按index索引的對象,也就是定義了__getitem__方法,比如list,str;
定義了__iter__方法。可以隨意返回。
可以調用iter(obj)的對象,並且返回一個iterator
iterator: 迭代器對象,也屬於python的名詞,只能迭代一次。需要滿足如下的迭代器協議
定義了__iter__方法,但是必須返回自身
定義了next方法,在python3.x是__next__。用來返回下一個值,並且當沒有資料了,拋出StopIteration
可以保持當前的狀態
首先str和list是iterable 但不是iterator:

In [3]: s = 'hi'In [4]: s.__getitem__Out[4]: In [5]: s.next # 沒有next方法---------------------------------------------------------------------------AttributeError       Traceback (most recent call last) in ()----> 1 s.nextAttributeError: 'str' object has no attribute 'next'In [6]: l = [1,2] # 同理In [7]: l.__iter__Out[7]: In [8]: l.next---------------------------------------------------------------------------AttributeError       Traceback (most recent call last) in ()----> 1 l.nextAttributeError: 'list' object has no attribute 'next'In [9]: iter(s) is s #iter() 沒有返回本身Out[9]: FalseIn [10]: iter(l) is l #同理Out[10]: False

但是對於iterator則不一樣如下, 另外iterable可以支援多次迭代,而iterator在多次next之後,再次調用就會拋異常,只可以迭代一次。

In [13]: si = iter(s)In [14]: siOut[14]: In [15]: si.__iter__ # 有__iter__Out[15]: In [16]: si.next #擁有nextOut[16]: In [20]: si.__iter__() is si #__iter__返回自己Out[20]: True

這樣,由這幾個例子可以解釋清楚這幾個概念的區別。

自訂iterator 與資料分離

說到這裡,迭代器對象基本出來了。下面大致說一下,如何讓自訂的類的對象成為迭代器對象,其實就是定義__iter__和next方法:

In [1]: %pasteclass DataIter(object): def __init__(self, *args):  self.data = list(args)  self.ind = 0 def __iter__(self): #返回自身  return self def next(self): # 返回資料  if self.ind == len(self.data):   raise StopIteration  else:   data = self.data[self.ind]   self.ind += 1   return data## -- End pasted text --In [9]: d = DataIter(1,2)In [10]: for x in d: # 開始迭代 ....:  print x ....:12In [13]: d.next() # 只能迭代一次,再次使用則會拋異常---------------------------------------------------------------------------StopIteration        Traceback (most recent call last)----> 1 d.next() in next(self)  10  def next(self):  11   if self.ind == len(self.data):---> 12    raise StopIteration  13   else:  14    data = self.data[self.ind]

從next函數中只能向前取資料,一次取一個可以看出來,不過不能重複取資料,那這個可不可以解決呢?

我們知道iterator只能迭代一次,但是iterable對象則沒有這個限制,因此我們可以把iterator從資料中分離出來,分別定義一個iterable與iterator如下:

class Data(object): # 只是iterable:可迭代對象而不iterator:迭代器 def __init__(self, *args):  self.data = list(args) def __iter__(self): # 並沒有返回自身  return DataIterator(self)class DataIterator(object): # iterator: 迭代器 def __init__(self, data):  self.data = data.data  self.ind = 0 def __iter__(self):  return self def next(self):  if self.ind == len(self.data):   raise StopIteration  else:   data = self.data[self.ind]   self.ind += 1   return dataif __name__ == '__main__': d = Data(1, 2, 3) for x in d:  print x, for x in d:  print x,

輸出就是:

1,2,3
1,2,3
可以看出來資料可以複用,因為每次都返回一個DataIterator,但是資料卻可以這樣使用,這種實現方式很常見,比如xrange的實現便是這種資料與迭代分離的形式,但是很節省記憶體,如下:

In [8]: sys.getsizeof(range(1000000))Out[8]: 8000072In [9]: sys.getsizeof(xrange(1000000))Out[9]: 40

另外有個小tips, 就是為什麼可以使用for 迭代迭代器對象,原因就是for替我們做了next的活,以及接收StopIteration的處理。

迭代器大概就記錄到這裡了,下面開始一個特殊的更加優雅的迭代器: 產生器

產生器(generator)

首先需要明確的就是產生器也是iterator迭代器,因為它遵循了迭代器協議.

兩種建立方式

包含yield的函數

產生器函數跟普通函數只有一點不一樣,就是把 return 換成yield,其中yield是一個文法糖,內部實現了迭代器協議,同時保持狀態可以掛起。如下:

def gen(): print 'begin: generator' i = 0 while True:  print 'before return ', i  yield i  i += 1  print 'after return ', ia = gen()In [10]: a #只是返回一個對象Out[10]: In [11]: a.next() #開始執行begin: generatorbefore return 0Out[11]: 0In [12]: a.next()after return 1before return 1Out[12]: 1

首先看到while True 不必驚慌,它只會一個一個的執行~
看結果可以看出一點東西:

調用gen()並沒有真實執行函數,而是只是返回了一個產生器對象
執行第一次a.next()時,才真正執行函數,執行到yield一個傳回值,然後就會掛起,保持當前的名字空間等狀態。然後等待下一次的調用,從yield的下一行繼續執行。
還有一種情況也會執行產生器函數,就是當檢索產生器的元素時,如list(generator), 說白了就是當需要資料的時候,才會執行。

In [15]: def func(): ....:  print 'begin' ....:  for i in range(4): ....:   yield iIn [16]: a = func()In [17]: list(a) #檢索資料,開始執行beginOut[17]: [0, 1, 2, 3]

yield還有其他進階應用程式,後面再慢慢學習。

產生器運算式

清單產生器十分方便:如下,求10以內的奇數:
[i for i in range(10) if i % 2]

同樣在python 2.4也引入了產生器運算式,而且形式非常類似,就是把[]換成了().

In [18]: a = ( i for i in range(4))In [19]: aOut[19]:  at 0x7f40c2cfe410>In [20]: a.next()Out[20]: 0

可以看出產生器運算式建立了一個產生器,而且生有個特點就是惰性計算, 只有在被檢索時候,才會被賦值。
之前有篇文章:python 預設參數問題及一個應用,最後有一個例子:

def multipliers(): return (lambda x : i * x for i in range(4)) #修改成產生器print [m(2) for m in multipliers()]

這個就是說,只有在執行m(2)的時候,產生器運算式裡面的for才會開始從0迴圈,然後接著才是i * x,因此不存在那篇文章中的問題。

惰性計算這個特點很有用,上述就是一個應用,2gua這樣說的:

性計算想像成水龍頭,需要的時候開啟,接完水了關掉,這時候資料流就暫停了,再需要的時候再開啟水龍頭,這時候資料仍是接著輸出,不需要從頭開始迴圈
其實本質跟迭代器差不多,不一次性把資料都那過來,需要的時候,才拿。

回到例子

看到這裡,開始的例子應該大概可以有點清晰了,核心語句就是:

for n in [1, 10]: base = (add(i, n) for i in base)

在執行list(base)的時候,開始檢索,然後產生器開始運算了。關鍵是,這個迴圈次數是2,也就是說,有兩次產生器運算式的過程。必須牢牢把握住這一點。

產生器返回去開始運算,n = 10而不是1沒問題吧,這個在上面提到的文章中已經提到了,就是add(i, n)綁定的是n這個變數,而不是它當時的數值。

然後首先是第一次產生器運算式的執行過程:base = (10 + 0, 10 + 1, 10 + 2, 10 +3),這是第一次迴圈的結果(形象表示,其實已經計算出來了(10,11,12,3)),然後第二次,base = (10 + 10, 11 + 10, 12 + 10, 13 + 10) ,終於得到結果了[20, 21, 22, 23].

具體執行過程可以在pythontutor上手動看看執行過程。

小結

概括
主要介紹了大概這樣幾點:

1.iterable,iterator與itertion的概念
2.迭代器協議
自訂可迭代對象與迭代器分離,保證資料複用
3.產生器: 特殊的迭代器,內部實現了迭代器協議

其實這一塊, 那幾個概念搞清楚, ,這個很關鍵, 搞懂了後面就水到渠成了。而且對之前的知識也有很多加深。
比如常見list就是iterator與iteable分離實現的,本身是可迭代對象,但不是迭代器, 類似與xrange,但是又不同。
越來越明白,看源碼的重要性了。 有地方寫的不合適的, 請指正。

參考

http://www.shutupandship.com/2012/01/understanding-python-iterables-and.html
http://www.learningpython.com/2009/02/23/iterators-iterables-and-generators-oh-my/
http://stackoverflow.com/questions/9884132/what-exactly-are-pythons-iterator-iterable-and-iteration-protocols
http://python.jobbole.com/81881/

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.