python的迭代器與生成器實例詳解

本文以實例詳解了python的迭代器與生成器,具體以下所示:python

1. 迭代器概述:shell

 

迭代器是訪問集合元素的一種方式。迭代器對象從集合的第一個元素開始訪問,直到全部的元素被訪問完結束。迭代器只能往前不會後退,不過這也沒什麼,由於人們不多在迭代途中日後退。bash

 

1.1 使用迭代器的優勢數據結構

 

對於原生支持隨機訪問的數據結構(如tuple、list),迭代器和經典for循環的索引訪問相比並沒有優點,反而丟失了索引值(可使用內建函數enumerate()找回這個索引值)。但對於沒法隨機訪問的數據結構(好比set)而言,迭代器是惟一的訪問元素的方式。app

另外,迭代器的一大優勢是不要求事先準備好整個迭代過程當中全部的元素。迭代器僅僅在迭代到某個元素時才計算該元素,而在這以前或以後,元素能夠不存在或者被銷燬。這個特色使得它特別適合用於遍歷一些巨大的或是無限的集合,好比幾個G的文件,或是斐波那契數列等等。ide

迭代器更大的功勞是提供了一個統一的訪問集合的接口,只要定義了__iter__()方法對象,就可使用迭代器訪問。函數

 

迭代器有兩個基本的方法對象

 

next方法:返回迭代器的下一個元素索引

__iter__方法:返回迭代器對象自己接口

下面用生成斐波那契數列爲例子,說明爲什麼用迭代器

 

示例代碼1

def fab(max): 
 n, a, b = 0, 0, 1 
 while n < max: 
   print b 
   a, b = b, a + b  
   n = n + 1

直接在函數fab(max)中用print打印會致使函數的可複用性變差,由於fab返回None。其餘函數沒法得到fab函數返回的數列。

 

示例代碼2

def fab(max): 
 L = []
 n, a, b = 0, 0, 1 
 while n < max: 
   L.append(b) 
   a, b = b, a + b  
   n = n + 1
 return L

代碼2知足了可複用性的需求,可是佔用了內存空間,最好不要。

 

示例代碼3

 

對比:

 
for i in range(1000): pass
for i in xrange(1000): pass

前一個返回1000個元素的列表,然後一個在每次迭代中返回一個元素,所以可使用迭代器來解決複用可佔空間的問題

class Fab(object): 
 def __init__(self, max): 
   self.max = max 
   self.n, self.a, self.b = 0, 0, 1 
  
 def __iter__(self): 
   return self 
  
 def next(self): 
   if self.n < self.max: 
     r = self.b 
     self.a, self.b = self.b, self.a + self.b  
     self.n = self.n + 1 
     return r 
   raise StopIteration()
執行

>>> for key in Fabs(5): 

  print key

Fabs 類經過 next() 不斷返回數列的下一個數,內存佔用始終爲常數  

1.2 使用迭代器

使用內建的工廠函數iter(iterable)能夠獲取迭代器對象:

>>> lst = range(5)
>>> it = iter(lst)
>>> it
<listiterator object at 0x01A63110>

使用next()方法能夠訪問下一個元素:

>>> it.next() 
   
>>> it.next() 
   
>>> it.next()

python處理迭代器越界是拋出StopIteration異常

>>> it.next()
   
>>> it.next
<method-wrapper 'next' of listiterator object at 0x01A63110> 
>>> it.next()
   
>>> it.next()
   
Traceback (most recent call last):
 File "<pyshell#27>", line 1, in <module>
  it.next()
StopIteration

瞭解了StopIteration,可使用迭代器進行遍歷了

lst = range(5)
it = iter(lst)
try:
  while True:
    val = it.next()
    print val
except StopIteration: 
  pass

事實上,由於迭代器如此廣泛,python專門爲for關鍵字作了迭代器的語法糖。在for循環中,Python將自動調用工廠函數iter()得到迭代器,自動調用next()獲取元素,還完成了檢查StopIteration異常的工做。以下

>>> a = (1, 2, 3, 4) 
>>> for key in a:
  print key

首先python對關鍵字in後的對象調用iter函數迭代器,而後調用迭代器的next方法得到元素,直到拋出StopIteration異常。

1.3 定義迭代器

 

下面一個例子——斐波那契數列

# -*- coding: cp936 -*-
class Fabs(object):
  def __init__(self,max):
    self.max = max
    self.n, self.a, self.b = 0, 0, 1 #特別指出:第0項是0,第1項是第一個1.整個數列從1開始
  def __iter__(self):
    return self
  def next(self):
    if self.n < self.max:
      r = self.b
      self.a, self.b = self.b, self.a + self.b
      self.n = self.n + 1
      return r
    raise StopIteration()
   
print Fabs(5)
for key in Fabs(5):
  print key

結果

<__main__.Fabs object at 0x01A63090>

2. 迭代器

帶有 yield 的函數在 Python 中被稱之爲 generator(生成器),幾個例子說明下(仍是用生成斐波那契數列說明)

 

能夠看出代碼3遠沒有代碼1簡潔,生成器(yield)既能夠保持代碼1的簡潔性,又能夠保持代碼3的效果

 

示例代碼4 

def fab(max):
  n, a, b = 0, 0, 1
  while n < max:
    yield b
    a, b = b, a + b 
    n = n = 1

執行

>>> for n in fab(5): 
  print n

簡單地講,yield 的做用就是把一個函數變成一個 generator,帶有 yield 的函數再也不是一個普通函數,Python 解釋器會將其視爲一個 generator,調用 fab(5) 不會執行 fab 函數,而是返回一個 iterable 對象!在 for 循環執行時,每次循環都會執行 fab 函數內部的代碼,執行到 yield b 時,fab 函數就返回一個迭代值,下次迭代時,代碼從 yield b 的下一條語句繼續執行,而函數的本地變量看起來和上次中斷執行前是徹底同樣的,因而函數繼續執行,直到再次遇到 yield。看起來就好像一個函數在正常執行的過程當中被 yield 中斷了數次,每次中斷都會經過 yield 返回當前的迭代值。

 

也能夠手動調用 fab(5) 的 next() 方法(由於 fab(5) 是一個 generator 對象,該對象具備 next() 方法),這樣咱們就能夠更清楚地看到 fab 的執行流程:

>>> f = fab(3)
>>> f.next()
>>> f.next()
>>> f.next()
>>> f.next()
   
Traceback (most recent call last):
 File "<pyshell#62>", line 1, in <module> 
  f.next()
StopIteration

return做用

在一個生成器中,若是沒有return,則默認執行到函數完畢;若是遇到return,若是在執行過程當中 return,則直接拋出 StopIteration 終止迭代。例如

>>> s = fab(5)
>>> s.next()
>>> s.next()
   
Traceback (most recent call last):
 File "<pyshell#66>", line 1, in <module> 
  s.next()
StopIteration

示例代碼5  文件讀取

def read_file(fpath): 
 BLOCK_SIZE = 1024 
 with open(fpath, 'rb') as f: 
   while True: 
     block = f.read(BLOCK_SIZE)  
     if block: 
       yield block 
     else: 
       return

若是直接對文件對象調用 read() 方法,會致使不可預測的內存佔用。好的方法是利用固定長度的緩衝區來不斷讀取文件內容。經過 yield,咱們再也不須要編寫讀文件的迭代類,就能夠輕鬆實現文件讀取。

相關文章
相關標籤/搜索