您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“python生成器怎么定義和使用”,內容詳細,步驟清晰,細節處理妥當,希望這篇“python生成器怎么定義和使用”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
生成器(英文:generator)是一個非常迷人的東西,也常被認為是 Python 的高級編程技能。不過,我依然很
樂意在這里跟讀者——盡管你可能是一個初學者——探討這個話題,因為我相信各位大佬看本教程的目的,絕非僅僅 將自己限制于初學者水平,一定有一顆不羈的心——要成為 Python 高手。那么,開始了解生成器吧。
還記得上節的“迭代器”嗎?生成器和迭代器有著一定的淵源關系。生成器必須是可迭代的,誠然它又不僅僅是
迭代器,但除此之外,又沒有太多的別的用途,所以,我們可以把它理解為非常方便的自定義迭代器。
>>> my_generator = (x*x for x in range(4))
這是不是跟列表解析很類似呢?仔細觀察,它不是列表,如果這樣的得到的才是列表:
>>> my_list = [x*x for x in range(4)]
以上兩的區別在于是 [] 還是 () ,雖然是細小的差別,但是結果完全不一樣。
>>> dir(my_generator) ['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'next', 'send', 'throw']
為了容易觀察,我將上述結果進行了重新排版。是不是發現了在迭代器中必有的方法 __inter__() 和 next() ,這說明它是迭代器。如果是迭代器,就可以用 for 循環來依次讀出其值
>>> for i in my_generator: ... print i ... 0 1 4 9 >>> for i in my_generator: ... print i ...
當第一遍循環的時候,將 my_generator 里面的值依次讀出并打印,但是,當再讀一次的時候,就發現沒有任何結果。這種特性也正是迭代器所具有的。
如果對那個列表,就不一樣了:
>>> for i in my_list: ... print i ... 0 1 4 9 >>> for i in my_list: ... print i ... 0 1 4 9
難道生成器就是把列表解析中的 [] 換成 () 就行了嗎?這僅僅是生成器的一種表現形式和使用方法罷了,仿照
列表解析式的命名,可以稱之為“生成器解析式”(或者:生成器推導式、生成器表達式)。
生成器解析式是有很多用途的,在不少地方替代列表,是一個不錯的選擇。特別是針對大量值的時候,如上節所說的,列表占內存較多,迭代器(生成器是迭代器)的優勢就在于少占內存,因此無需將生成器(或者說是迭代器)實例化為一個列表,直接對其進行操作,方顯示出其迭代的優勢。比如:
>>> sum(i*i for i in range(10)) 285
注意觀察上面的 sum() 運算,不要以為里面少了一個括號,就是這么寫。是不是很迷人?如果列表,你
不得不:
>>> sum([i*i for i in range(10)]) 285
通過生成器解析式得到的生成器,掩蓋了生成器的一些細節,并且適用領域也有限。下面就要剖析生成器的內部,深入理解這個魔法工具。
yield 這個詞在漢語中有“生產、出產”之意,在 Python 中,它作為一個關鍵詞(你在變量、函數、類的名稱中
就不能用這個了),是生成器的標志。
>>> def g(): ... yield 0 ... yield 1 ... yield 2 ... >>> g <function g at 0xb71f3b8c>
建立了一個非常簡單的函數,跟以往看到的函數唯一不同的地方是用了三個 yield 語句。然后進行下面的操作:
>>> ge = g() >>> ge <generator object g at 0xb7200edc> >>> type(ge) <type 'generator'>
上面建立的函數返回值是一個生成器(generator)類型的對象。
>>> dir(ge) ['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'next', 'send', 'throw']
在這里看到了 __iter__() 和 next() ,說明它是迭代器。既然如此,當然可以:
>>> ge.next() 0 >>> ge.next() 1 >>> ge.next() 2 >>> ge.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
從這個簡單例子中可以看出,那個含有 yield 關鍵詞的函數返回值是一個生成器類型的對象,這個生成器對象就是迭代器。
我們把含有 yield 語句的函數稱作生成器。生成器是一種用普通函數語法定義的迭代器。通過上面的例子可以看出,這個生成器(也是迭代器),在定義過程中并沒有像上節迭代器那樣寫 __inter__() 和 next() ,而是只要用了 yield 語句,那個普通函數就神奇般地成為了生成器,也就具備了迭代器的功能特性。
yield 語句的作用,就是在調用的時候返回相應的值。詳細剖析一下上面的運行過程:
1. ge = g() :除了返回生成器之外,什么也沒有操作,任何值也沒有被返回。
2. ge.next() :直到這時候,生成器才開始執行,遇到了第一個 yield 語句,將值返回,并暫停執行(有的稱之
為掛起)。
3. ge.next() :從上次暫停的位置開始,繼續向下執行,遇到 yield 語句,將值返回,又暫停。
4. gen.next() :重復上面的操作。
5. gene.next() :從上面的掛起位置開始,但是后面沒有可執行的了,于是 next() 發出異常。
從上面的執行過程中,發現 yield 除了作為生成器的標志之外,還有一個功能就是返回值。那么它跟 return 這個返回值有什么區別呢?
為了弄清楚 yield 和 return 的區別,我寫了兩個函數來掩飾:
>>> def r_return(n): ... print "You taked me." ... while n > 0: ... print "before return" ... return n ... n -= 1 ... print "after return" ... >>> rr = r_return(3) You taked me. before return >>> rr 3
從函數被調用的過程可以清晰看出, rr = r_return(3) ,函數體內的語句就開始執行了,遇到 return,將值返
回,然后就結束函數體內的執行。所以 return 后面的語句根本沒有執行。這是 return 的特點
下面將 return 改為 yield:
>>> def y_yield(n): ... print "You taked me." ... while n > 0: ... print "before yield" ... yield n ... n -= 1 ... print "after yield" ... >>> yy = y_yield(3) #沒有執行函數體內語句 >>> yy.next() #開始執行 You taked me. before yield 3 #遇到 yield,返回值,并暫停 >>> yy.next() #從上次暫停位置開始繼續執行 after yield before yield 2 #又遇到 yield,返回值,并暫停 >>> yy.next() #重復上述過程 after yield before yield 1 >>> yy.next() after yield #沒有滿足條件的值,拋出異常 Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
結合注釋和前面對執行過程的分析,讀者一定能理解 yield 的特點了,也深知與 return 的區別了。
一般的函數,都是止于 return。作為生成器的函數,由于有了 yield,則會遇到它掛起,如果還有 return,遇到它就直接拋出 SoptIteration 異常而中止迭代。
#!/usr/bin/env Python # coding=utf-8 def fibs(max): """ 斐波那契數列的生成器 """ n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 if __name__ == "__main__": f = fibs(10) for i in f: print i ,
運行結果如下:
$ python 21501.py 1 1 2 3 5 8 13 21 34 55
用生成器方式實現的斐波那契數列是不是跟以前的有所不同了呢?大家可以將本教程中已經演示過的斐波那契數列實現方式做一下對比,體會各種方法的差異。
經過上面的各種例子,已經明確,一個函數中,只要包含了 yield 語句,它就是生成器,也是迭代器。這種方式顯然比前面寫迭代器的類要簡便多了。但,并不意味著上節的就被拋棄。是生成器還是迭代器,都是根據具體的使用情景而定。
在 python2.5 以后,生成器有了一個新特征,就是在開始運行后能夠為生成器提供新的值。這就好似生成器
和“外界”之間進行數據交流。
>>> def repeater(n): ... while True: ... n = (yield n) ... >>> r = repeater(4) >>> r.next() 4 >>> r.send("hello") 'hello
當執行到 r.next() 的時候,生成器開始執行,在內部遇到了 yield n 掛起。注意在生成器函數中, n = (yield
n) 中的 yield n 是一個表達式,并將結果賦值給 n,雖然不嚴格要求它必須用圓括號包裹,但是一般情況都這
么做,請大家也追隨這個習慣。
當執行 r.send("hello") 的時候,原來已經被掛起的生成器(函數)又被喚醒,開始執行 n = (yield n) ,也就是
講 send() 方法發送的值返回。這就是在運行后能夠為生成器提供值的含義。
如果接下來再執行 r.next() 會怎樣?
>>> r.next()
什么也沒有,其實就是返回了 None。按照前面的敘述,讀者可以看到,這次執行 r.next() ,由于沒有傳入任何值,yield 返回的就只能是 None.
還要注意,send() 方法必須在生成器運行后并掛起才能使用,也就是 yield 至少被執行一次。如果不是這樣:
>>> s = repeater(5) >>> s.send("how") Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can't send non-None value to a just-started generator
就報錯了。但是,可將參數設為 None:
>>> s.send(None) 5
這是返回的是調用函數的時傳入的值。
此外,還有兩個方法:close() 和 throw()
? throw(type, value=None, traceback=None):用于在生成器內部(生成器的當前掛起處,或未啟動時在定
義處)拋出一個異常(在 yield 表達式中)。
? close():調用時不用參數,用于關閉生成器。
讀到這里,這篇“python生成器怎么定義和使用”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。