當咱們建立了一個列表的時候,就建立了一個能夠迭代的對象:python
>>> squares=[n*n for n in range(3)] >>> for i in squares: print i 0 1 4
這種建立列表的操做很常見,稱爲列表推導。可是像列表這樣的迭代器,好比str、file等,雖然用起來很方便,但有一點,它們是儲存在內存中的,若是值很大,會很麻煩。網絡
而生成器表達式不一樣,它執行的計算與列表包含相同,但會迭代的生成結果。它的語法與列表推導同樣,只是要用小括號來代替中括號:多線程
>>> squares=(n*n for n in range(3)) >>> for i in squares: print i 0 1 4
生成器表達式不會建立序列形式的對象,不會把全部的值都讀取到內存中,而是會建立一個經過迭代並按照需求生成值的生成器對象(Generator)。併發
那麼,還有沒有其它方法來產生生成器呢?app
例若有個需求,要生成斐波那契數列的前10位,咱們能夠這樣寫:異步
def fib(n): result=[] a=1 b=1 result.append(a) for i in range(n-1): a,b=b,a+b result.append(a) return result if __name__=='__main__': print fib(10)
數字不多時,函數運行良好,但數字不少時,問題就來了,顯然生成一個幾千幾萬長度的列表並非一個很好的主意。分佈式
這樣,需求就變成了:寫一個能夠生成可迭代對象的函數,或者說,不要讓函數一次返回所有的值,而是一次返回一個值。函數
這好像與咱們的常識相違背,當咱們調用一個普通的Python函數時,通常是從函數的第一行代碼開始執行,結束於return語句、異常或者函數結束(能夠看做隱式的返回None):oop
def fib(n): a=1 b=1 for i in range(n-1): a,b=b,a+b return a if __name__=='__main__': print fib(10) >>> 1 #返回第一個值時就卡住了
函數一旦將控制權交還給調用者,就意味着所有結束。函數中作的全部工做以及保存在局部變量中的數據都將丟失。再次調用這個函數時,一切都將從頭建立。函數只有一次返回結果的機會,於是必須一次返回全部的結果。一般咱們都這麼認爲的。可是,若是它們並不是如此呢?請看神奇的yield:線程
def fib(n): a=1 yield a b=1 for i in range(n-1): a,b=b,a+b yield a if __name__=='__main__': for i in fib(10): print i >>> 1 1 2 3 5 8 13 21 34
python中生成器的定義很簡單,使用了yield關鍵字的函數就能夠稱之爲生成器,它生成一個值的序列:
def countdown(n): while n>0: yield n n-=1 if __name__=='__main__': for i in countdown(10): print i
生成器函數返回生成器。要注意的是生成器就是一類特殊的迭代器。做爲一個迭代器,生成器必需要定義一些方法,其中一個就是__next__()。如同迭代器同樣,咱們可使用next()函數(Python3是__next__() )來獲取下一個值:
>>> c=countdown(10) >>> c.next() 10 >>> c.next() 9
每當生成器被調用的時候,它會返回一個值給調用者。在生成器內部使用yield來完成這個動做。爲了記住yield到底幹了什麼,最簡單的方法是把它看成專門給生成器函數用的特殊的return。調用next()時,生成器函數不斷的執行語句,直至遇到yield爲止,此時生成器函數的"狀態"會被凍結,全部的變量的值會被保留下來,下一行要執行的代碼的位置也會被記錄,直到再次調用next()繼續執行yield以後的語句。
next()不能無限執行,當迭代結束時,會拋出StopIteration異常。迭代未結束時,若是你想結束生成器,可使用close()方法。
>>> c.next() 1 >>> c.next() StopIteration >>> c=countdown(10) >>> c.next() 10 >>> c.close() >>> c.next() StopIteration
yield語句還有更給力的功能,做爲一個語句出如今賦值運算符的右邊,接受一個值,或同時生成一個值並接受一個值。
def recv(): print 'Ready' while True: n=yield print 'Go %s'%n >>> c=recv() >>> c.next() Ready >>> c.send(1) Go 1 >>> c.send(2) Go 2
以這種方式使用yield語句的函數稱爲協程。在這個例子中,對於next()的初始調用是必不可少的,這樣協程才能執行可通向第一個yield表達式的語句。在這裏協程會掛起,等待相關生成器對象send()方法給它發送一個值。傳遞給send()的值由協程中的yield表達式返回。
協程的運行通常是無限期的,使用方法close()能夠顯式的關閉它。
若是yield表達式中提供了值,協程可使用yield語句同時接收和發出返回值。
def split_line(): print 'ready to split' result=None while True: line=yield result result=line.split() >>> s=split_line() >>> s.next() ready to split >>> s.send('1 2 3') ['1', '2', '3'] >>> s.send('a b c') ['a', 'b', 'c']
注意:理解這個例子中的前後順序很是重要。首個next()方法讓協程執行到yield result,這將返回result的值None。在接下來的send()調用中,接收到的值被放到line中並拆分到result中。send()方法的返回值就是下一條yield語句的值。也就是說,send()方法能夠將一個值傳遞給yield表達式,可是其返回值來自下一個yield表達式,而不是接收send()傳遞的值的yield表達式。
若是你想用send()方法來開啓協程的執行,必須先send一個None值,由於這時候是沒有yield語句來接受值的,不然就會拋出異常。
>>> s=split_line() >>> s.send('1 2 3') TypeError: can't send non-None value to a just-started generator >>> s=split_line() >>> s.send(None) ready to split
乍看之下,如何使用生成器和協程解決實際問題彷佛並不明顯。但在解決系統、網絡和分佈式計算方面的某些問題時,生成器和協程特別有用。實際上,yield已經成爲Python最強大的關鍵字之一。
好比,要創建一個處理文件的管道:
import os,sys def default_next(func): def start(*args,**kwargs): f=func(*args,**kwargs) f.next() return f return start @default_next def find_files(target): topdir=yield while True: for path,dirname,filelist in os.walk(topdir): for filename in filelist: target.send(os.path.join(path,filename)) @default_next def opener(target): while True: name=yield f=open(name) target.send(f) @default_next def catch(target): while True: f=yield for line in f: target.send(line) @default_next def printer(): while True: line=yield print line
而後將這些協程鏈接起來,就能夠建立一個數據流處理管道了:
finder=find_files(opener(catch(printer()))) finder.send(toppath)
程序的執行徹底由將數據發送到第一個協程find_files()中來驅動,協程管道會永遠保持活動狀態,直到它顯式的調用close()。
總之,生成器的功能很是強大。協程能夠用於實現某種形式的併發。在某些類型的應用程序中,能夠用一個任務調度器和一些生成器或協程實現協做式用戶空間多線程,即greenlet。yield的威力將在協程,協同式多任務處理(cooperative multitasking),以及異步IO中獲得真正的體現。