溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

yield和Generators的示例分析

發(fā)布時間:2020-11-25 11:17:35 來源:億速云 閱讀:133 作者:小新 欄目:編程語言

這篇文章給大家分享的是有關yield和Generators的示例分析的內容。小編覺得挺實用的,因此分享給大家做個參考。一起跟隨小編過來看看吧。

生成器

生成器是通過一個或多個yield表達式構成的函數(shù),每一個生成器都是一個迭代器(但是迭代器不一定是生成器)。

如果一個函數(shù)包含yield關鍵字,這個函數(shù)就會變?yōu)橐粋€生成器。

生成器并不會一次返回所有結果,而是每次遇到y(tǒng)ield關鍵字后返回相應結果,并保留函數(shù)當前的運行狀態(tài),等待下一次的調用。

由于生成器也是一個迭代器,那么它就應該支持next方法來獲取下一個值。(也可以使用.__next__()屬性, 在python2 中是.next())

協(xié)程與子例程

我們調用一個普通的Python函數(shù)時,一般是從函數(shù)的第一行代碼開始執(zhí)行,結束于return語句、異?;蛘吆瘮?shù)結束(可以看作隱式的返回None)。一旦函數(shù)將控制權交還給調用者,就意味著全部結束。函數(shù)中做的所有工作以及保存在局部變量中的數(shù)據(jù)都將丟失。再次調用這個函數(shù)時,一切都將從頭創(chuàng)建。 

對于在計算機編程中所討論的函數(shù),這是很標準的流程。這樣的函數(shù)只能返回一個值,不過,有時可以創(chuàng)建能產生一個序列的函數(shù)還是有幫助的。要做到這一點,這種函數(shù)需要能夠“保存自己的工作”。 

我說過,能夠“產生一個序列”是因為我們的函數(shù)并沒有像通常意義那樣返回。return隱含的意思是函數(shù)正將執(zhí)行代碼的控制權返回給函數(shù)被調用的地方。而"yield"的隱含意思是控制權的轉移是臨時和自愿的,我們的函數(shù)將來還會收回控制權。

在Python中,擁有這種能力的“函數(shù)”被稱為生成器,它非常的有用。生成器(以及yield語句)最初的引入是為了讓程序員可以更簡單的編寫用來產生值的序列的代碼。 以前,要實現(xiàn)類似隨機數(shù)生成器的東西,需要實現(xiàn)一個類或者一個模塊,在生成數(shù)據(jù)的同時保持對每次調用之間狀態(tài)的跟蹤。引入生成器之后,這變得非常簡單。

為了更好的理解生成器所解決的問題,讓我們來看一個例子。在了解這個例子的過程中,請始終記住我們需要解決的問題:生成值的序列。

注意:在Python之外,最簡單的生成器應該是被稱為協(xié)程(coroutines)的東西。在本文中,我將使用這個術語。請記住,在Python的概念中,這里提到的協(xié)程就是生成器。Python正式的術語是生成器;協(xié)程只是便于討論,在語言層面并沒有正式定義。

例子:有趣的素數(shù)

假設你的老板讓你寫一個函數(shù),輸入?yún)?shù)是一個int的list,返回一個可以迭代的包含素數(shù)1 的結果。

記住,迭代器(Iterable) 只是對象每次返回特定成員的一種能力。

你肯定認為"這很簡單",然后很快寫出下面的代碼:

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 的一種實現(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 的實現(xiàn)完全滿足了需求,所以我們告訴老板已經搞定了。她反饋說我們的函數(shù)工作正常,正是她想要的。

處理無限序列

噢,真是如此嗎?過了幾天,老板過來告訴我們她遇到了一些小問題:她打算把我們的get_primes函數(shù)用于一個很大的包含數(shù)字的list。實際上,這個list非常大,僅僅是創(chuàng)建這個list就會用完系統(tǒng)的所有內存。為此,她希望能夠在調用get_primes函數(shù)時帶上一個start參數(shù),返回所有大于這個參數(shù)的素數(shù)(也許她要解決 Project Euler problem 10)。

我們來看看這個新需求,很明顯只是簡單的修改get_primes是不可能的。 自然,我們不可能返回包含從start到無窮的所有的素數(shù)的列表 (雖然有很多有用的應用程序可以用來操作無限序列)??瓷先ビ闷胀ê瘮?shù)處理這個問題的可能性比較渺茫。

在我們放棄之前,讓我們確定一下最核心的障礙,是什么阻止我們編寫滿足老板新需求的函數(shù)。通過思考,我們得到這樣的結論:函數(shù)只有一次返回結果的機會,因而必須一次返回所有的結果。得出這樣的結論似乎毫無意義;“函數(shù)不就是這樣工作的么”,通常我們都這么認為的??墒?,不學不成,不問不知,“如果它們并非如此呢?”

想象一下,如果get_primes可以只是簡單返回下一個值,而不是一次返回全部的值,我們能做什么?我們就不再需要創(chuàng)建列表。沒有列表,就沒有內存的問題。由于老板告訴我們的是,她只需要遍歷結果,她不會知道我們實現(xiàn)上的區(qū)別。

不幸的是,這樣做看上去似乎不太可能。即使是我們有神奇的函數(shù),可以讓我們從n遍歷到無限大,我們也會在返回第一個值之后卡住:

def get_primes(start):
    for element in magical_infinite_range(start):
        if is_prime(element):
            return element

假設這樣去調用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中,一上來就會碰到輸入等于3的,并且在函數(shù)的第4行返回。與直接返回不同,我們需要的是在退出時可以為下一次請求準備一個值。

不過函數(shù)做不到這一點。當函數(shù)返回時,意味著全部完成。我們保證函數(shù)可以再次被調用,但是我們沒法保證說,“呃,這次從上次退出時的第4行開始執(zhí)行,而不是常規(guī)的從第一行開始”。函數(shù)只有一個單一的入口:函數(shù)的第1行代碼。

走進生成器

這類問題極其常見以至于Python專門加入了一個結構來解決它:生成器。一個生成器會“生成”值。創(chuàng)建一個生成器幾乎和生成器函數(shù)的原理一樣簡單。

一個生成器函數(shù)的定義很像一個普通的函數(shù),除了當它要生成一個值的時候,使用yield關鍵字而不是return。如果一個def的主體包含yield,這個函數(shù)會自動變成一個生成器(即使它包含一個return)。除了以上內容,創(chuàng)建一個生成器沒有什么多余步驟了。

生成器函數(shù)返回生成器的迭代器。這可能是你最后一次見到“生成器的迭代器”這個術語了, 因為它們通常就被稱作“生成器”。要注意的是生成器就是一類特殊的迭代器。作為一個迭代器,生成器必須要定義一些方法(method),其中一個就是__next__()【注意: 在python2中是: next() 方法】。如同迭代器一樣,我們可以使用next()函數(shù)來獲取下一個值。

為了從生成器獲取下一個值,我們使用next()函數(shù),就像對付迭代器一樣。

(next()會操心如何調用生成器的__next__()方法)。既然生成器是一個迭代器,它可以被用在for循環(huán)中。

每當生成器被調用的時候,它會返回一個值給調用者。在生成器內部使用yield來完成這個動作(例如yield 7)。為了記住yield到底干了什么,最簡單的方法是把它當作專門給生成器函數(shù)用的特殊的return(加上點小魔法)。**

yield就是專門給生成器用的return(加上點小魔法)。

下面是一個簡單的生成器函數(shù):

>>> def simple_generator_function():
>>>    yield 1
>>>    yield 2
>>>    yield 3

這里有兩個簡單的方法來使用它:

>>> 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

魔法

那么神奇的部分在哪里?我很高興你問了這個問題!當一個生成器函數(shù)調用yield,生成器函數(shù)的“狀態(tài)”會被凍結,所有的變量的值會被保留下來,下一行要執(zhí)行的代碼的位置也會被記錄,直到再次調用next()。一旦next()再次被調用,生成器函數(shù)會從它上次離開的地方開始。如果永遠不調用next(),yield保存的狀態(tài)就被無視了。

我們來重寫get_primes()函數(shù),這次我們把它寫作一個生成器。注意我們不再需要magical_infinite_range函數(shù)了。使用一個簡單的while循環(huán),我們創(chuàng)造了自己的無窮串列。

def get_primes(number):
    while True:
        if is_prime(number):
            yield number
        number += 1

如果生成器函數(shù)調用了return,或者執(zhí)行到函數(shù)的末尾,會出現(xiàn)一個StopIteration異常。 這會通知next()的調用者這個生成器沒有下一個值了(這就是普通迭代器的行為)。這也是這個while循環(huán)在我們的get_primes()函數(shù)出現(xiàn)的原因。如果沒有這個while,當我們第二次調用next()的時候,生成器函數(shù)會執(zhí)行到函數(shù)末尾,觸發(fā)StopIteration異常。一旦生成器的值用完了,再調用next()就會出現(xiàn)錯誤,所以你只能將每個生成器的使用一次。下面的代碼是錯誤的:

>>> our_generator = simple_generator_function()
>>> for value in our_generator:
>>>     print(value)
>>> # 我們的生 call last):
  File "<ipython-input-13-7e48a609051a>", line 1, in <module>
    next(our_generator)
StopIteration
>>> # 然而,我們總可以再創(chuàng)建一個生成器
>>> # 只需再次調用生成器函數(shù)即可
>>> new_generator = simple_generator_function()
>>> print(next(new_generator)) # 工作正常
1

因此,這個while循環(huán)是用來確保生成器函數(shù)永遠也不會執(zhí)行到函數(shù)末尾的。只要調用next()這個生成器就會生成一個值。這是一個處理無窮序列的常見方法(這類生成器也是很常見的)。

執(zhí)行流程

讓我們回到調用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)中對get_primes的調用,觀察一下前幾個元素是如何創(chuàng)建的有助于我們的理解。當for循環(huán)從get_primes請求第一個值時,我們進入get_primes,這時與進入普通函數(shù)沒有區(qū)別。

進入第三行的while循環(huán)

停在if條件判斷(3是素數(shù))

通過yield將3和執(zhí)行控制權返回給solve_number_10

接下來,回到insolve_number_10:

for循環(huán)得到返回值3

for循環(huán)將其賦給next_prime

total加上next_prime

for循環(huán)從get_primes請求下一個值

這次,進入get_primes時并沒有從開頭執(zhí)行,我們從第5行繼續(xù)執(zhí)行,也就是上次離開的地方。

def get_primes(number):
    while True:
        if is_prime(number):
            yield number
        number += 1 # <<<<<<<<<<

最關鍵的是,number還保持我們上次調用yield時的值(例如3)。記住,yield會將值傳給next()的調用方,同時還會保存生成器函數(shù)的“狀態(tài)”。接下來,number加到4,回到while循環(huán)的開始處,然后繼續(xù)增加直到得到下一個素數(shù)(5)。我們再一次把number的值通過yield返回給solve_number_10的for循環(huán)。這個周期會一直執(zhí)行,直到for循環(huán)結束(得到的素數(shù)大于2,000,000)。

總結

關鍵點:

generator是用來產生一系列值的

yield則像是generator函數(shù)的返回結果

yield唯一所做的另一件事就是保存一個generator函數(shù)的狀態(tài)

generator就是一個特殊類型的迭代器(iterator)

和迭代器相似,我們可以通過使用next()來從generator中獲取下一個值

通過隱式地調用next()來忽略一些值

感謝各位的閱讀!關于yield和Generators的示例分析就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經查實,將立刻刪除涉嫌侵權內容。

AI