python生成器

工作手记發表於2024-07-30

一 前言

環境: python3.10 win10

二 生成器

1 關於生成器

先看一個例子
image

定義了一個函式,當我們執行該函式時,並未像普通函式那樣執行函式體內的程式碼
從其中的英文可知,執行函式得到了一個生成器物件,這個生成器物件也叫做generator iterator(生成器迭代器),generator iterator也屬於前面說的迭代器,通常也叫做生成器(generator)

像這種包含關鍵字yield的函式,嚴格來說叫做生成器函式,

前面的迭代器介紹過,可用next()不斷返回迭代器中的元素或者用在for迴圈中,生成器物件既然屬於迭代器,這裡也嘗試一下
image

可以看到,每執行一次next(),就可以執行函式內的程式碼到遇到關鍵字yield處暫停,並返回yield value中的value作為next()的返回值,再次執行時,會從上次暫停處繼續執行到下一個yield處暫停,如此迴圈,一直到最後沒有yield時引發StopIteration異常(就像迭代器一樣,一直呼叫next(),最後沒有可返回的資料時引發StopIteration異常)

執行生成器時除了可以使用next(generator),也可以使用generator.send(xxxx)
image

如上,第一次執行生成器時,如採用方法send(),則必須傳遞引數None
如果不是第一次(即恢復執行生成器時),則可以在方法send(value)中新增引數value,value將傳進生成器內作為yield 表示式的值,看一個例子
image

如上,利用send(None)開始執行生成器時,執行到第一個yield表示式暫停,此時只執行了表示式右邊即yield 0 這一部分,左邊部分給v的賦值時沒有執行的。

第2次執行send(value)時,第一處yield左邊部分給v的賦值才開始執行(value的值作為v的值),然後暫停到第2處yield的位置,第2處yield處v的賦值要到下一次執行時間,如此迴圈

所以照此推理,第一次執行send()時,應該是第0處的yield處的左邊部分賦值開始執行,但第0處是不存在的,所以,第一次的引數必須為None即send(None)

這裡需要注意的是,yield value中的value是從生成器傳到外面作為next()或者send(xxx)的結果
,而send(value)中的value是傳到生成器裡面去替換掉yield xxx作為v的值

2 生成器的一些方法

  • generator__next__:
    此方法通常是隱式呼叫,透過for迴圈或者內建的next()函式

  • generator.send(value):-

  • generator.throw(type, value, traceback)
    在生成器暫停位置(yield處)引發一個異常(傳入一個異常到生成器裡面去,讓生成器帶著異常繼續執行程式碼)
    若異常發生後,生成器能繼續產生下一個值,則將下一個值作為throw()的結果。此時相當於是send(value),只不過value引數是一個異常
    若異常發生後,生成器沒有繼續產生下一個值,則將引發StopIteration異常,從而退出執行
    如果生成器函式沒有捕獲傳入的異常,或是引發了另一個異常,則該異常會被傳播給呼叫方即throw()處

type: 一個異常類
value: 可選引數
traceback: 可選引數

  • generator.close()
    在生成器暫停位置(yield處)引發GeneratorExit從而關閉生成器的執行(同樣是傳遞一個異常到生成器裡面去)
    注意,GeneratorExit直接繼承自 BaseException 而不是 Exception,不會像Exception類異常那樣(Exception類異常未被處理時,則會顯示該異常資訊)
    和throw()類似的是,傳入該異常後,生成器若沒有繼續產生下一個值,GeneratorExit只會停止程式碼的執行並返回到呼叫方即close()處
    傳入該異常後,如果生成器產生了下一個值,則將引發異常 RuntimeError,但不會像throw()那一樣得到下一個值作為結果
    如果生成器引發了任何其他異常,它將被傳播給呼叫方

image
如上,我們傳進去一個異常ZeroDivisionError後,異常被except 語句捕獲,excep與finally中 都沒有yield關鍵字能產生下一個值,所以在finally語句後引發了異常StopIteration,該異常被傳遞給呼叫方,即throw()方法的執行處

image
如上,和上面一個例子的異常捕獲稍微有點區別。在死迴圈裡面放置了一個異常捕獲的try語句。
這意味著只要沒有異常或者符合捕獲條件的異常,該迴圈依然可以繼續執行下去

上面利用throw()方法傳進去異常ZeroDivisionError,這個異常類屬於Exception的子類,所以執行了Exception中的程式碼,然後繼續執行迴圈,遇到yield產生了生成器的下一個值,所以沒有引發上一個例子的異常StopIteration,繼續執行迴圈

後面執行close()方法傳遞進去一個GeneratorExit異常,然後繼續執行,由於該異常不屬於Exception型別,所以就跳出了死迴圈,將異常傳給了外面的try語句,然後執行了finally。
這種異常不會顯示異常資訊,所以就沒像上個例子那樣在後面顯示異常資訊

image
如上,這個例子利用except BaseException 捕獲到了傳進去的GeneratorExit,並在後面的執行中遇到了yield,也就是生成器產生了下一個值,所以這裡引發了異常RuntimeError
但這裡沒有像throw()那樣,把產生的下一個值返回作為close()的結果

四 生成器例子

image
執行結果
image

相關文章