一 概要
在了解Python的數據結構時,容器(container)、可迭代對象(iterable)、迭代器(iterator)、生成器(generator)、列表/集合/字典推導式(list,set,dict comprehension)眾多概念參雜在一起,難免讓初學者一頭霧水,我將用一篇文章試圖將這些概念以及它們之間的關系捋清楚
二 容器(container)
容器是一種把多個元素組織在一起的數據結構,容器中的元素可以逐個地迭代獲取,可以用 in , not in 關鍵字判斷元素是否包含在容器中。通常這類數據結構把所有的元素存儲在內存中(也有一些特列並不是所有的元素都放在內存)在Python中,常見的容器對象有:
- list, deque, ....
- set, frozensets, ....
- dict, defaultdict, OrderedDict, Counter, ....
- tuple, namedtuple, …
- str
容器比較容易理解,因為你就可以把它看作是一個盒子、一棟房子、一個櫃子,里面可以塞任何東西。從技術角度來說,當它可以用來詢問某個元素是否包含在其中時,那么這個對象就可以認為是一個容器,比如 list,set,tuples都是容器對象:
盡管絕大多數容器都提供了某種方式來獲取其中的每一個元素,但這並不是容器本身提供的能力,而是 可迭代對象 賦予了容器這種能力,當然並不是所有的容器都是可迭代的。
三 可迭代對象(iterable)
如果給定一個list或tuple,我們可以通過for
循環來遍歷這個list或tuple,這種遍歷我們稱為迭代(Iteration)。
剛才說過,很多容器都是可迭代對象,此外還有更多的對象同樣也是可迭代對象,比如處於打開狀態的files,sockets等等。但凡是可以返回一個 迭代器 的對象都可稱之為可迭代對象,聽起來可能有點困惑,沒關系,可迭代對象與迭代器有一個非常重要的區別。先看一個例子:
這里 x 是一個可迭代對象,可迭代對象和容器一樣是一種通俗的叫法,並不是指某種具體的數據類型,list是可迭代對象,dict是可迭代對象,set也是可迭代對象。 y 和 z 是兩個獨立的迭代器,迭代器內部持有一個狀態,該狀態用於記錄當前迭代所在的位置,以方便下次迭代的時候獲取正確的元素。迭代器有一種具體的迭代器類型,比如 list_iterator , set_iterator 。可迭代對象實現了 __iter__ 和 __next__ 方法(python2中是 next 方法,python3是 __next__ 方法),這兩個方法對應內置函數 iter() 和 next() 。 __iter__ 方法返回可迭代對象本身,這使得他既是一個可迭代對象同時也是一個迭代器。
四 迭代器(iterator)
那么什么迭代器呢?它是一個帶狀態的對象,他能在你調用 next() 方法的時候返回容器中的下一個值,任何實現了 __next__() (python2中實現 next() )方法的對象都是迭代器,至於它是如何實現的這並不重要。
現在我們就以斐波那契數列()為例,學習為何創建以及如何創建一個迭代器:
著名的斐波拉契數列(Fibonacci),除第一個和第二個數外,任意一個數都可由前兩個數相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...

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函數返回的數列。

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滿足了可復用性的需求,但是占用了內存空間,最好不要。
對比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 1 1 2 3 5 '''
Fabs 類通過 next() 不斷返回數列的下一個數,內存占用始終為常數
Fib既是一個可迭代對象(因為它實現了 __iter__ 方法),又是一個迭代器(因為實現了 __next__ 方法)。實例變量 self .a 和 self.b 用戶維護迭代器內部的狀態。每次調用 next() 方法的時候做兩件事:
- 為下一次調用 next() 方法修改狀態
- 為當前這次調用生成返回結果
迭代器就像一個懶加載的工廠,等到有人需要的時候才給它生成值返回,沒調用的時候就處於休眠狀態等待下一次調用。
五 for i in (iterable)的內部實現
在大多數情況下,我們不會一次次調用next方法去取值,而是通過 for i in (iterable),
注意:in后面的對象如果是一個迭代器,內部因為有iter方法才可以進行操作,所以,迭代器協議里面有iter和next兩個方法,否則for語句無法應用。
注意:
for i in range(10): print i :定時垃圾回收機制:沒有引用指向這個對象,則被回收
六 生成器(generator)
生成器算得上是Python語言中最吸引人的特性之一,生成器其實是一種特殊的迭代器,不過這種迭代器更加優雅。代碼3遠沒有代碼1簡潔,生成器(yield)既可以保持代碼1的簡潔性,又可以保持代碼3的效果。它不需要再像上面的類一樣寫 __iter__() 和 __next__() 方法了,只需要一個 yiled 關鍵字。 生成器有如下特征是它一定也是迭代器(反之不成立),因此任何生成器也是以一種懶加載的模式生成值。用生成器來實現斐波那契數列的例子是:
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 1 1 2 3 5
fib 就是一個普通的python函數,它特需的地方在於函數體中沒有 return 關鍵字,函數的返回值是一個生成器對象。當執行 f=fib(5) 返回的是一個生成器對象,此時函數體中的代碼並不會執行,只有顯示或隱示地調用next的時候才會真正執行里面的代碼。
yield 的作用就是把一個函數變成一個 generator,帶有 yield 的函數不再是一個普通函數,Python 解釋器會將其視為一個 generator,在 for 循環執行時,每次循環都會執行 fab 函數內部的代碼,執行到 yield b 時,fab 函數就返回一個迭代值,下次迭代時,代碼從 yield b 的下一條語句繼續執行,而函數的本地變量看起來和上次中斷執行前是完全一樣的,於是函數繼續執行,直到再次遇到 yield。看起來就好像一個函數在正常執行的過程中被 yield 中斷了數次,每次中斷都會通過 yield 返回當前的迭代值。
也可以手動調用 fab(5) 的 next() 方法(因為 fab(5) 是一個 generator 對象,該對象具有 next() 方法),這樣我們就可以更清楚地看到 fab 的執行流程:
>>> f = fab(3) >>> f.__next__() 1 >>> f.__next__() 1 >>> f.__next__() 2 >>> f.__next__() Traceback (most recent call last): File "<pyshell#62>", line 1, in <module> f.next() StopIteration
需要明確的就是生成器也是iterator迭代器,因為它遵循了迭代器協議.
兩種創建方式
包含yield的函數
生成器函數跟普通函數只有一點不一樣,就是把 return 換成yield,其中yield是一個語法糖,內部實現了迭代器協議,同時保持狀態可以掛起。如下:
return:
在一個生成器中,如果沒有return,則默認執行到函數完畢;如果遇到return,如果在執行過程中 return,則直接拋出 StopIteration 終止迭代.

def f(): yield 5
print("ooo") return
yield 6
print("ppp") # if str(tem)=='None':
# print("ok")
f=f() # print(f.__next__()) # print(f.__next__())
for i in f: print(i) ''' return即迭代結束 for不報錯的原因是內部處理了迭代結束的這種情況 '''
注意:
文件讀取
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,我們不再需要編寫讀文件的迭代類,就可以輕松實現文件讀取。
My:生成器對象就是一種特殊的迭代器對象,滿足迭代器協議,可以調用next;對生成器對象for 循環時,調用iter方法返回了生成器對象,然后再不斷next迭代,而iter和next都是在yield內部實現的。
練習1:使用文件讀取,找出文件中最長的行的?

max(len(x.strip()) for x in open('/hello/abc','r'))
練習2:
def add(s, x): return s + x def gen(): for i in range(4): yield i base = gen() for n in [1, 10]: base = (add(i, n) for i in base) print list(base)

''' 核心語句就是: 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]. '''
練習3:自定義range
七 生成器的擴展
生成器對象支持幾個方法,如gen.next() ,gen.send() ,gen.throw()等。
由於沒有額外的yield,所以將直接拋出StopIteration。
send的工作方式:
def f(): print("ok") s=yield 7 print(s) yield 8 f=f() print(f.send(None)) print(next(f)) #print(f.send(None))等同於print(next(f)),執行流程:打印ok,yield7,當再next進來時:將None賦值給s,然后返回8,可以通過斷點來觀察
協程應用:
所謂協同程序也就是是可以掛起,恢復,有多個進入點。其實說白了,也就是說多個函數可以同時進行,可以相互之間發送消息等。

import queue def tt(): for x in range(4): print ('tt'+str(x) ) yield def gg(): for x in range(4): print ('xx'+str(x) ) yield class Task(): def __init__(self): self._queue = queue.Queue() def add(self,gen): self._queue.put(gen) def run(self): while not self._queue.empty(): for i in range(self._queue.qsize()): try: gen= self._queue.get() gen.send(None) except StopIteration: pass else: self._queue.put(gen) t=Task() t.add(tt()) t.add(gg()) t.run() # tt0 # xx0 # tt1 # xx1 # tt2 # xx2 # tt3 # xx3
參考:
http://anandology.com/python-practice-book/iterators.html
http://www.cnblogs.com/kaituorensheng/p/3826911.html
http://www.jb51.net/article/80740.htm
http://www.open-open.com/lib/view/open1463668934647.html