python生成器详解
python生成器和python裝飾器幾乎平起平坐,在python世界中占有重要的地位。如果說python裝飾器是為了讓程序員悄無聲息拓展函數(shù)功能,那么python生成器就是為了讓你的代碼更省資源,更高效!說實話python生成器的抽象程度和靈活程度都要比python裝飾器高,功能也更加強大,所以我也只能暫時比較粗略地說句“python生成器就像一把瑞士軍刀,讓你的代碼更加短小(簡潔)精悍(有力)”。
首先我們來看一段python2環(huán)境下運行的一段代碼:
打印結果如下:
0 1 2 ---------- 0 1 2 ---------- 0 1 4 ---------- 0 1 4看了這段代碼,你可能會說:python真麻煩,有range又有xrange,有[]又有(),運行結果還一樣。這不是白費事嘛?
那我們現(xiàn)在稍微改動下代碼:
打印結果如下:
<type 'list'> [0, 1, 2] (0, 1) --------- <type 'xrange'> xrange(3) (0, 1) --------- <type 'list'> <type 'generator'>這里需要說明:range()返回一個列表list,xrange()返回一個生成器;也即[]返回一個列表list而()返回一個生成器。
* 注意:用[]推導出來的是迭代器(Iterables)。用()推導出來的是生成器(Generators)。*
可迭代對象(iterable) 與 迭代器(iterator)
iterable可迭代對象是實現(xiàn)了__iter__()方法的對象。更確切的說,是container.__iter__()方法,該方法返回的是的一個iterator對象,因此你可以從iterable可迭代對象獲得iterator迭代器。在python中,迭代通常是通過for … in …來完成的,而且只要是可迭代對象(iterable),都能進行迭代。
例如,在下面的遍歷中
for value in something這個something就是一個可迭代對象(iterable)。比如list,string,file。
對于iterable,我們該關注的是,它是一個能一次返回一個成員的對象(iterable is an object capable of returning its members one at a time),一些iterable將所有值都存儲在內存中,比如list,而另一些并不是這樣,比如我們下面將講到的iterator。
iterator(迭代器)是這樣的對象:實現(xiàn)了無參的__next__() 方法,返回序列中的下一個元素;如果沒有元素,將會拋出StopIteration異常。Python的迭代器也實現(xiàn)了__iter__()方法,所以一個迭代器(iterator)也是可迭代的(iterable)。iterator.next()是iterator區(qū)別于iterable的關鍵了,它允許我們顯式地獲取一個元素.當調用next()方法時,實際上產生了2個操作:
二者的關系如下 :
iter(可迭代對象) 生成迭代器
>>> s = 'abc' #s是一個可迭代對象 >>> it = iter(s) #it是一個迭代器 >>> for i in it: ... print(i)我們現(xiàn)在不用for循環(huán)遍歷這個迭代器,我們嘗試調用next()方法。
>>> s = 'abc' >>> it = iter(s) >>> next(it) 'a' >>> s = 'abc' >>> it = iter(s) >>> next(it) 'a' >>> next(it) 'b' >>> next(it) 'c' >>> next(it) Traceback (most recent call last):File "<stdin>", line 1, in <module> StopIteration >>>除了最后一行拋出異常外,其他結果和for循環(huán)一樣。也就是說,用for循環(huán)遍歷迭代器(iterator)X時,會循環(huán)地調用X的next()方法取得每一次的值,直到iterator為空,返回的StopIteration作為循環(huán)結束的標志。for … in … 會自動處理StopIteration異常,從而避免了拋出異常而使程序中斷。
我們對一個可迭代對象(iterable)用for … in …進行迭代時,實際是先通過調用iter()方法得到一個iterator,假設叫做X.然后循環(huán)地調用X的next()方法取得每一次的值,直到iterator為空,返回的StopIteration作為循環(huán)結束的標志.for … in … 會自動處理StopIteration異常,從而避免了拋出異常而使程序中斷.
需要格外注意的是,iterator是消耗型的,即每一個值被使用過后,就消失了.因此,你可以將以上的操作2理解成pop.對iterator進行遍歷之后,其就變成了一個空的容器了,但不等于None哦.因此,若要重復使用iterator,利用list()方法將其結果保存起來是一個不錯的選擇。
代碼如下:
生成器概念
只要 Python 函數(shù)的定義體中有 yield 關鍵字,該函數(shù)就是生成器函數(shù)。調用生成器函數(shù)時,會返回一個生成器對象。也就是說,生成器函數(shù)是生成器工廠。
函數(shù)返回一個值,而生成器函數(shù)生成一個值。不僅如此,函數(shù)返回值后就退出了,而生成器可以生產多次值,生成器函數(shù)中還能出現(xiàn)多個yield。
如何“生成”生成器
如何產生一個生成器呢,方法有二:
1.生成器函數(shù)(generator function):帶有yield關鍵字的函數(shù)就是生成器函數(shù),它的返回值是個生成器。
2.生成器表達式(generator expression):而形如(elem for elem in [1, 2, 3])的表達式,稱為generator expression,也能產生一個生成器。
生成器表達式生成生成器:
>>> gen = ( i for i in [2,3,4,5,6]) >>> gen <generator object <genexpr> at 0x00000178D648D1A8> >>> for i in gen: ... print(i) ... 2 3 4 5 6我們調用next()方法,也是可以的:
>>> gen = ( i for i in [2,3,4,5,6]) >>> next(gen) 2 >>> next(gen) 3 >>> next(gen) 4 >>> next(gen) 5 >>> next(gen) 6 >>> next(gen) Traceback (most recent call last):File "<stdin>", line 1, in <module> StopIteration這和我們上面迭代器(iterator)的使用一毛一樣!
其實,生成器(generator)就是迭代器(iterator)的一種,以更優(yōu)雅的方式實現(xiàn)的iterator,而且完全可以像使用iterator一樣使用generator。當然除了定義,定義一個iterator,你需要分別實現(xiàn)_ _ iter _ _ ()方法和 _ _ next_ _()方法,但generator只需要一個yield關鍵字就可以。
生成器函數(shù)產生生成器
>>> def gen123(): ... yield 1 ... yield 2 ... yield 3 ... >>> gen123 <function gen123 at 0x00000178D6614D08> >>> gen123() <generator object gen123 at 0x00000178D648D1A8> >>> for i in gen123(): ... print(i) ... 1 2 3這里你也能猜出來,我們調用next()方法除了會拋出異常,效果也一樣:
>>> def gen123(): ... yield 1 ... yield 2 ... yield 3 ... >>> gen = gen123() >>> next(gen) 1 >>> next(gen) 2 >>> next(gen) 3 >>> next(gen) Traceback (most recent call last):File "<stdin>", line 1, in <module> StopIteration其實 ,for循環(huán)會每次隱時調用next()方法,前進到函數(shù)中的下一個yield語句處。
生成器函數(shù)執(zhí)行過程
生成器另外一個黑科技是生成器中的yield語句會掛起該生成器函數(shù)的狀態(tài),保留足夠的信息,以便之后從它離開的地方繼續(xù)執(zhí)行。
我們來看代碼:
>>> def genAB(): #? ... print('start') ... yield 'A' #? ... print('continue') ... yield 'B' #? ... print('end') #? ... >>> for i in genAB(): #? ... print('--->',i) #? ...打印結果:
start ---> A continue ---> B end? 定義生成器函數(shù)的方式與普通的函數(shù)無異,只不過要使用 yield 關鍵字。
? 在 for 循環(huán)中第一次隱式調用 next() 函數(shù)時(序號?),會打印
‘start’,然后停在第一個 yield 語句,生成值 ‘A’。
? 在 for 循環(huán)中第二次隱式調用 next() 函數(shù)時,會打印
‘continue’,然后停在第二個 yield 語句,生成值 ‘B’。
? 第三次調用 next() 函數(shù)時,會打印 ‘end.’,然后到達函數(shù)定義體
的末尾,導致生成器對象拋出 StopIteration 異常。
? 迭代時,for 機制的作用與 g = iter(gen_AB()) 一樣,用于獲取
生成器對象,然后每次迭代時調用 next(g)。
? 循環(huán)塊打印 –> 和 next(g) 返回的值。但是,生成器函數(shù)中的
print 函數(shù)輸出結果之后才會看到這個輸出。
? ‘start’ 是生成器函數(shù)定義體中 print(‘start’) 輸出的結果。
? 生成器函數(shù)定義體中的 yield ‘A’ 語句會生成值 A,提供給 for 循
環(huán)使用,而 A 會賦值給變量 i,最終輸出 –> A。
? 第二次調用 next(g),繼續(xù)迭代,生成器函數(shù)定義體中的代碼由
yield ‘A’ 前進到 yield ‘B’。文本 continue 是由生成器函數(shù)定義
體中的第二個 print 函數(shù)輸出的。
? yield ‘B’ 語句生成值 B,提供給 for 循環(huán)使用,而 B 會賦值給變
量 c,所以循環(huán)打印出 –> B。
? 第三次調用 next(it),繼續(xù)迭代,前進到生成器函數(shù)的末尾。文本
end. 是由生成器函數(shù)定義體中的第三個 print 函數(shù)輸出的。到達生成
器函數(shù)定義體的末尾時,生成器對象拋出 StopIteration 異常。for
機制會捕獲異常,因此循環(huán)終止時沒有報錯。
總結
以上是生活随笔為你收集整理的python生成器详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 单例模式-序列号生成器
- 下一篇: Python检查批量URL是否可以正常访