這篇文章給大家分享的是有關(guān)yield和Generators的示例分析的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考。一起跟隨小編過來看看吧。
目前創(chuàng)新互聯(lián)公司已為上千多家的企業(yè)提供了網(wǎng)站建設(shè)、域名、網(wǎng)站空間、網(wǎng)站托管、服務(wù)器托管、企業(yè)網(wǎng)站設(shè)計(jì)、石龍網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長(zhǎng),共同發(fā)展。
生成器
生成器是通過一個(gè)或多個(gè)yield表達(dá)式構(gòu)成的函數(shù),每一個(gè)生成器都是一個(gè)迭代器(但是迭代器不一定是生成器)。
如果一個(gè)函數(shù)包含yield關(guān)鍵字,這個(gè)函數(shù)就會(huì)變?yōu)橐粋€(gè)生成器。
生成器并不會(huì)一次返回所有結(jié)果,而是每次遇到y(tǒng)ield關(guān)鍵字后返回相應(yīng)結(jié)果,并保留函數(shù)當(dāng)前的運(yùn)行狀態(tài),等待下一次的調(diào)用。
由于生成器也是一個(gè)迭代器,那么它就應(yīng)該支持next方法來獲取下一個(gè)值。(也可以使用.__next__()屬性, 在python2 中是.next())
協(xié)程與子例程
我們調(diào)用一個(gè)普通的Python函數(shù)時(shí),一般是從函數(shù)的第一行代碼開始執(zhí)行,結(jié)束于return語句、異?;蛘吆瘮?shù)結(jié)束(可以看作隱式的返回None)。一旦函數(shù)將控制權(quán)交還給調(diào)用者,就意味著全部結(jié)束。函數(shù)中做的所有工作以及保存在局部變量中的數(shù)據(jù)都將丟失。再次調(diào)用這個(gè)函數(shù)時(shí),一切都將從頭創(chuàng)建。
對(duì)于在計(jì)算機(jī)編程中所討論的函數(shù),這是很標(biāo)準(zhǔn)的流程。這樣的函數(shù)只能返回一個(gè)值,不過,有時(shí)可以創(chuàng)建能產(chǎn)生一個(gè)序列的函數(shù)還是有幫助的。要做到這一點(diǎn),這種函數(shù)需要能夠“保存自己的工作”。
我說過,能夠“產(chǎn)生一個(gè)序列”是因?yàn)槲覀兊暮瘮?shù)并沒有像通常意義那樣返回。return隱含的意思是函數(shù)正將執(zhí)行代碼的控制權(quán)返回給函數(shù)被調(diào)用的地方。而"yield"的隱含意思是控制權(quán)的轉(zhuǎn)移是臨時(shí)和自愿的,我們的函數(shù)將來還會(huì)收回控制權(quán)。
在Python中,擁有這種能力的“函數(shù)”被稱為生成器,它非常的有用。生成器(以及yield語句)最初的引入是為了讓程序員可以更簡(jiǎn)單的編寫用來產(chǎn)生值的序列的代碼。 以前,要實(shí)現(xiàn)類似隨機(jī)數(shù)生成器的東西,需要實(shí)現(xiàn)一個(gè)類或者一個(gè)模塊,在生成數(shù)據(jù)的同時(shí)保持對(duì)每次調(diào)用之間狀態(tài)的跟蹤。引入生成器之后,這變得非常簡(jiǎn)單。
為了更好的理解生成器所解決的問題,讓我們來看一個(gè)例子。在了解這個(gè)例子的過程中,請(qǐng)始終記住我們需要解決的問題:生成值的序列。
注意:在Python之外,最簡(jiǎn)單的生成器應(yīng)該是被稱為協(xié)程(coroutines)的東西。在本文中,我將使用這個(gè)術(shù)語。請(qǐng)記住,在Python的概念中,這里提到的協(xié)程就是生成器。Python正式的術(shù)語是生成器;協(xié)程只是便于討論,在語言層面并沒有正式定義。
例子:有趣的素?cái)?shù)
假設(shè)你的老板讓你寫一個(gè)函數(shù),輸入?yún)?shù)是一個(gè)int的list,返回一個(gè)可以迭代的包含素?cái)?shù)1 的結(jié)果。
記住,迭代器(Iterable) 只是對(duì)象每次返回特定成員的一種能力。
你肯定認(rèn)為"這很簡(jiǎn)單",然后很快寫出下面的代碼:
def get_primes(input_list): result_list = list() for element in input_list: if is_prime(element): result_list.append() return result_list # 或者更好一些的... def get_primes(input_list): return (element for element in input_list if is_prime(element)) # 下面是 is_prime 的一種實(shí)現(xiàn)... def is_prime(number): if number > 1: if number == 2: return True if number % 2 == 0: return False for current in range(3, int(math.sqrt(number) + 1), 2): if number % current == 0: return False return True return False
上面 is_prime 的實(shí)現(xiàn)完全滿足了需求,所以我們告訴老板已經(jīng)搞定了。她反饋說我們的函數(shù)工作正常,正是她想要的。
處理無限序列
噢,真是如此嗎?過了幾天,老板過來告訴我們她遇到了一些小問題:她打算把我們的get_primes函數(shù)用于一個(gè)很大的包含數(shù)字的list。實(shí)際上,這個(gè)list非常大,僅僅是創(chuàng)建這個(gè)list就會(huì)用完系統(tǒng)的所有內(nèi)存。為此,她希望能夠在調(diào)用get_primes函數(shù)時(shí)帶上一個(gè)start參數(shù),返回所有大于這個(gè)參數(shù)的素?cái)?shù)(也許她要解決 Project Euler problem 10)。
我們來看看這個(gè)新需求,很明顯只是簡(jiǎn)單的修改get_primes是不可能的。 自然,我們不可能返回包含從start到無窮的所有的素?cái)?shù)的列表 (雖然有很多有用的應(yīng)用程序可以用來操作無限序列)??瓷先ビ闷胀ê瘮?shù)處理這個(gè)問題的可能性比較渺茫。
在我們放棄之前,讓我們確定一下最核心的障礙,是什么阻止我們編寫滿足老板新需求的函數(shù)。通過思考,我們得到這樣的結(jié)論:函數(shù)只有一次返回結(jié)果的機(jī)會(huì),因而必須一次返回所有的結(jié)果。得出這樣的結(jié)論似乎毫無意義;“函數(shù)不就是這樣工作的么”,通常我們都這么認(rèn)為的。可是,不學(xué)不成,不問不知,“如果它們并非如此呢?”
想象一下,如果get_primes可以只是簡(jiǎn)單返回下一個(gè)值,而不是一次返回全部的值,我們能做什么?我們就不再需要?jiǎng)?chuàng)建列表。沒有列表,就沒有內(nèi)存的問題。由于老板告訴我們的是,她只需要遍歷結(jié)果,她不會(huì)知道我們實(shí)現(xiàn)上的區(qū)別。
不幸的是,這樣做看上去似乎不太可能。即使是我們有神奇的函數(shù),可以讓我們從n遍歷到無限大,我們也會(huì)在返回第一個(gè)值之后卡?。?/p>
def get_primes(start): for element in magical_infinite_range(start): if is_prime(element): return element
假設(shè)這樣去調(diào)用get_primes:
def solve_number_10(): # She *is* working on Project Euler #10, I knew it! total = 2 for next_prime in get_primes(3): if next_prime < 2000000: total += next_prime else: print(total) return
顯然,在get_primes中,一上來就會(huì)碰到輸入等于3的,并且在函數(shù)的第4行返回。與直接返回不同,我們需要的是在退出時(shí)可以為下一次請(qǐng)求準(zhǔn)備一個(gè)值。
不過函數(shù)做不到這一點(diǎn)。當(dāng)函數(shù)返回時(shí),意味著全部完成。我們保證函數(shù)可以再次被調(diào)用,但是我們沒法保證說,“呃,這次從上次退出時(shí)的第4行開始執(zhí)行,而不是常規(guī)的從第一行開始”。函數(shù)只有一個(gè)單一的入口:函數(shù)的第1行代碼。
走進(jìn)生成器
這類問題極其常見以至于Python專門加入了一個(gè)結(jié)構(gòu)來解決它:生成器。一個(gè)生成器會(huì)“生成”值。創(chuàng)建一個(gè)生成器幾乎和生成器函數(shù)的原理一樣簡(jiǎn)單。
一個(gè)生成器函數(shù)的定義很像一個(gè)普通的函數(shù),除了當(dāng)它要生成一個(gè)值的時(shí)候,使用yield關(guān)鍵字而不是return。如果一個(gè)def的主體包含yield,這個(gè)函數(shù)會(huì)自動(dòng)變成一個(gè)生成器(即使它包含一個(gè)return)。除了以上內(nèi)容,創(chuàng)建一個(gè)生成器沒有什么多余步驟了。
生成器函數(shù)返回生成器的迭代器。這可能是你最后一次見到“生成器的迭代器”這個(gè)術(shù)語了, 因?yàn)樗鼈兺ǔ>捅环Q作“生成器”。要注意的是生成器就是一類特殊的迭代器。作為一個(gè)迭代器,生成器必須要定義一些方法(method),其中一個(gè)就是__next__()【注意: 在python2中是: next() 方法】。如同迭代器一樣,我們可以使用next()函數(shù)來獲取下一個(gè)值。
為了從生成器獲取下一個(gè)值,我們使用next()函數(shù),就像對(duì)付迭代器一樣。
(next()會(huì)操心如何調(diào)用生成器的__next__()方法)。既然生成器是一個(gè)迭代器,它可以被用在for循環(huán)中。
每當(dāng)生成器被調(diào)用的時(shí)候,它會(huì)返回一個(gè)值給調(diào)用者。在生成器內(nèi)部使用yield來完成這個(gè)動(dòng)作(例如yield 7)。為了記住yield到底干了什么,最簡(jiǎn)單的方法是把它當(dāng)作專門給生成器函數(shù)用的特殊的return(加上點(diǎn)小魔法)。**
yield就是專門給生成器用的return(加上點(diǎn)小魔法)。
下面是一個(gè)簡(jiǎn)單的生成器函數(shù):
>>> def simple_generator_function(): >>> yield 1 >>> yield 2 >>> yield 3
這里有兩個(gè)簡(jiǎn)單的方法來使用它:
>>> for value in simple_generator_function(): >>> print(value) 1 2 3 >>> our_generator = simple_generator_function() >>> next(our_generator) 1 >>> next(our_generator) 2 >>> next(our_generator) 3
魔法
那么神奇的部分在哪里?我很高興你問了這個(gè)問題!當(dāng)一個(gè)生成器函數(shù)調(diào)用yield,生成器函數(shù)的“狀態(tài)”會(huì)被凍結(jié),所有的變量的值會(huì)被保留下來,下一行要執(zhí)行的代碼的位置也會(huì)被記錄,直到再次調(diào)用next()。一旦next()再次被調(diào)用,生成器函數(shù)會(huì)從它上次離開的地方開始。如果永遠(yuǎn)不調(diào)用next(),yield保存的狀態(tài)就被無視了。
我們來重寫get_primes()函數(shù),這次我們把它寫作一個(gè)生成器。注意我們不再需要magical_infinite_range函數(shù)了。使用一個(gè)簡(jiǎn)單的while循環(huán),我們創(chuàng)造了自己的無窮串列。
def get_primes(number): while True: if is_prime(number): yield number number += 1
如果生成器函數(shù)調(diào)用了return,或者執(zhí)行到函數(shù)的末尾,會(huì)出現(xiàn)一個(gè)StopIteration異常。 這會(huì)通知next()的調(diào)用者這個(gè)生成器沒有下一個(gè)值了(這就是普通迭代器的行為)。這也是這個(gè)while循環(huán)在我們的get_primes()函數(shù)出現(xiàn)的原因。如果沒有這個(gè)while,當(dāng)我們第二次調(diào)用next()的時(shí)候,生成器函數(shù)會(huì)執(zhí)行到函數(shù)末尾,觸發(fā)StopIteration異常。一旦生成器的值用完了,再調(diào)用next()就會(huì)出現(xiàn)錯(cuò)誤,所以你只能將每個(gè)生成器的使用一次。下面的代碼是錯(cuò)誤的:
>>> our_generator = simple_generator_function() >>> for value in our_generator: >>> print(value) >>> # 我們的生 call last): File "", line 1, in next(our_generator) StopIteration >>> # 然而,我們總可以再創(chuàng)建一個(gè)生成器 >>> # 只需再次調(diào)用生成器函數(shù)即可 >>> new_generator = simple_generator_function() >>> print(next(new_generator)) # 工作正常 1
因此,這個(gè)while循環(huán)是用來確保生成器函數(shù)永遠(yuǎn)也不會(huì)執(zhí)行到函數(shù)末尾的。只要調(diào)用next()這個(gè)生成器就會(huì)生成一個(gè)值。這是一個(gè)處理無窮序列的常見方法(這類生成器也是很常見的)。
執(zhí)行流程
讓我們回到調(diào)用get_primes的地方:solve_number_10。
def solve_number_10(): # She *is* working on Project Euler #10, I knew it! total = 2 for next_prime in get_primes(3): if next_prime < 2000000: total += next_prime else: print(total) return
我們來看一下solve_number_10的for循環(huán)中對(duì)get_primes的調(diào)用,觀察一下前幾個(gè)元素是如何創(chuàng)建的有助于我們的理解。當(dāng)for循環(huán)從get_primes請(qǐng)求第一個(gè)值時(shí),我們進(jìn)入get_primes,這時(shí)與進(jìn)入普通函數(shù)沒有區(qū)別。
進(jìn)入第三行的while循環(huán)
停在if條件判斷(3是素?cái)?shù))
通過yield將3和執(zhí)行控制權(quán)返回給solve_number_10
接下來,回到insolve_number_10:
for循環(huán)得到返回值3
for循環(huán)將其賦給next_prime
total加上next_prime
for循環(huán)從get_primes請(qǐng)求下一個(gè)值
這次,進(jìn)入get_primes時(shí)并沒有從開頭執(zhí)行,我們從第5行繼續(xù)執(zhí)行,也就是上次離開的地方。
def get_primes(number): while True: if is_prime(number): yield number number += 1 # <<<<<<<<<<
最關(guān)鍵的是,number還保持我們上次調(diào)用yield時(shí)的值(例如3)。記住,yield會(huì)將值傳給next()的調(diào)用方,同時(shí)還會(huì)保存生成器函數(shù)的“狀態(tài)”。接下來,number加到4,回到while循環(huán)的開始處,然后繼續(xù)增加直到得到下一個(gè)素?cái)?shù)(5)。我們?cè)僖淮伟裯umber的值通過yield返回給solve_number_10的for循環(huán)。這個(gè)周期會(huì)一直執(zhí)行,直到for循環(huán)結(jié)束(得到的素?cái)?shù)大于2,000,000)。
總結(jié)
關(guān)鍵點(diǎn):
generator是用來產(chǎn)生一系列值的
yield則像是generator函數(shù)的返回結(jié)果
yield唯一所做的另一件事就是保存一個(gè)generator函數(shù)的狀態(tài)
generator就是一個(gè)特殊類型的迭代器(iterator)
和迭代器相似,我們可以通過使用next()來從generator中獲取下一個(gè)值
通過隱式地調(diào)用next()來忽略一些值
感謝各位的閱讀!關(guān)于yield和Generators的示例分析就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!