生成器函數(shù)在Python中與迭代器協(xié)議的概念聯(lián)系在一起。包含yield語句的函數(shù)會被特地編譯成生成器 !!!
當函數(shù)被調(diào)用時,他們返回一個生成器對象,這個對象支持迭代器接口。
不像一般的函數(shù)會生成值后退出,生成器函數(shù)在生成值后會自動掛起并暫停他們的執(zhí)行和狀態(tài),他的本地變量將保存狀態(tài)信息,這些信息在函數(shù)恢復(fù)時將再度有效
創(chuàng)建生成器方式有兩種:
方法一:
1 | s = ( x for x in range ( 5 ) ) |
方法二:
1 2 3 | def foo(): print ( 'OK' ) yield 1 |
例子:
1 2 3 4 5 6 | def g(n): for i in range (n): yield i * * 2 for i in g( 5 ): print (i) |
要了解他的運行原理,我們來用next方法看看:
1 2 3 4 5 6 7 8 9 10 11 | t = g( 5 ) print (t.__next__()) # 0 print (t.__next__()) # 1 print (t.__next__()) # 4 print (t.__next__()) # 9 print (t.__next__()) # 16 print (t.__next__()) Traceback (most recent call last): File "<stdin>" , line 1 , in <module> StopIteration |
在運行完5次next之后,生成器拋出了一個StopIteration異常,迭代終止。
send(msg) 與 next()
了解了next()如何讓包含yield的函數(shù)執(zhí)行后,我們再來看另外一個非常重要的函數(shù)send(msg)。
其實next()和send()在一定意義上作用是相似的,區(qū)別是send()可以傳遞yield表達式的值進去,而next()不能傳遞特定的值,只能傳遞None進去。因此,我們可以看做
c.next() 和 c.send(None) 作用是一樣的。
1 2 3 4 5 6 7 8 9 10 11 | def g(n): for i in range (n): ret = yield i * * 2 print (ret) t = g( 5 ) print (t.__next__()) print (t.send( 'Hello' )) # 0 Hello 1 |
需要注意的是,第一次調(diào)用時,請使用next()語句或是send(None),不能使用send發(fā)送一個非None的值,否則會出錯的,因為沒有yield語句來接收這個值。
send(msg) 和 next()是有返回值的,它們的返回值很特殊,返回的是yield表達式的參數(shù) !
執(zhí)行順序是:遇到y(tǒng)ield 先返回值,等下次再進入時再用 msg 進行賦值?。。?/p>
再來看一個yield的例子,用生成器生成一個Fibonacci數(shù)列:
1 2 3 4 5 6 7 8 9 10 | def fab( max ): a, b = 0 , 1 while a < max : yield a a, b = b, a + b for i in fab( 20 ): print (i) # 0 1 1 2 3 5 8 13 |
另一個 yield 的例子來源于文件讀取。
1 2 3 4 5 6 7 8 9 | 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 |
for循環(huán)可以用于Python中的任何類型,包括列表、元祖等等,實際上,for循環(huán)可用于任何“可迭代對象”!
迭代器是一個實現(xiàn)了迭代器協(xié)議的對象,Python中的迭代器協(xié)議就是有next方法的對象會前進到下一結(jié)果,而在一系列結(jié)果的末尾是,則會引發(fā)StopIteration。
任何這類的對象在Python中都可以用for循環(huán)或其他遍歷工具迭代,迭代工具內(nèi)部會在每次迭代時調(diào)用next方法,并且捕捉StopIteration異常來確定何時離開。
使用迭代器一個顯而易見的好處就是:每次只從對象中讀取一條數(shù)據(jù),不會造成內(nèi)存的過大開銷。
注意:
判斷迭代器的條件是:
有__iter__ 方法
有__next__ 方法
所有的生成器都是迭代器!
迭代器例子:
比如要逐行讀取一個文件的內(nèi)容,利用readlines()方法,我們可以這么寫:
1 2 | for line in open ( "test.txt" ).readlines(): print (line) |
這樣雖然可以工作,但不是最好的方法。因為他實際上是把文件一次加載到內(nèi)存中,然后逐行打印。當文件很大時,這個方法的內(nèi)存開銷就很大了。
利用file的迭代器,我們可以這樣寫:
1 2 | for line in open ( "test.txt" ): print (line) |
這是最簡單也是運行速度最快的寫法,他并沒顯式的讀取文件,而是利用迭代器每次讀取下一行。
for 循環(huán)實質(zhì):
調(diào)用 __iter__ 方法將可迭代對象轉(zhuǎn)換成迭代器
對迭代器對象不斷調(diào)用 __next__ 方法
處理StopIteration 異常
判斷條件:內(nèi)部具有 __iter__ 方法
1 2 3 4 5 6 | from collections import Iterable,Iterator print ( isinstance ( range ( 1 ),Iterable)) print ( isinstance ( range ( 1 ),Iterator)) print ( isinstance ( list (),Iterable)) print ( isinstance ( list (),Iterator)) |