1. 程式人生 > >python生成器(轉)

python生成器(轉)

gen see none erro 結果 true 接受 sequence 支持

生成器是一種特殊的叠代器,內部支持了生成器協議,不需要明確定義__iter__()和next()方法。生成器通過生成器函數產生,生成器函數可以通過常規的def語句來定義,但是不用return返回,而是用yield一次返回一個結果。

一、yield和叠代器生成器

叠代器是非常高效的類型,無論是從時間復雜度,還是從空間復雜度。而實現叠代器的代碼雖然簡單,卻也繁瑣。為此,python定義了一個yield關鍵字,專門用來構造叠代器。yield有生成,產生的意思。

yield的功能和return非常類似,它們都只能在方法中使用。不同的是,包含yield語句的方法被稱為生成器方法。當調用生成器方法時,會返回一個生成器對象。

例如,看下面的例子。

def MyGenerator():  
    yield 1  
  
gen = MyGenerator()  
print gen

輸出結果為

<generator object MyGenerator at 0x0000000001D9DD80>

當調用生成器對象的next方法時,會執行生成器方法中的代碼,直至遇到yield語句時,方法的執行過程會被掛起。同時,方法運行的上下文環境會被保存。而next方法的返回值就是yield關鍵字後面表達式的返回值。

例如,下面代碼

print gen.next()

執行結果為

1 

當我們繼續調用next方法時,從上一次掛起的地方開始,繼續執行後面的代碼。直至遇到下一個yield語句。當方法執行完畢,依然沒有遇到yield語句,拋出StopIteration異常。

例如

def MyGenerator():  
    yield 1  
    yield a  
  
gen = MyGenerator()  
print gen.next()  
print gen.next()  
print gen.next() 

上面代碼中第1次調用next方法,執行語句yield 1。第2次調用next方法,執行語句yield ‘a‘。第3次調用next方法時,在方法退出前都沒有遇到yield語句,因此拋出StopIteration異常。

上面介紹的生成器方法的工作機理。在後面的博文中,會逐步介紹生成器方法的一些經典應用。

二、通過生成器函數構造序列對象的叠代器

事實上,一個序列對象的叠代器,依賴於一個整數序列的叠代器。看下面的代碼

def MyGenerator(len):  
    start  = 0  
    while start < len:  
        yield start  
        start = start + 1  
  
  
gen = MyGenerator(3)  
print gen.next()  
print gen.next()  
print gen.next()  
print gen.next()  

當調用第1次next方法時, 會首先執行MyGenerator方法的第1行代碼start = 0。然後進入循環。這裏len的值通過參數傳入為3。因此while的條件表達式為真。進入循環後,遇到yield語句,方法的執行過程被掛起。next方法的返回值為start的值,即0。

當調用第2次next方法時,接著上面的掛起點,往下執行start = start + 1語句,start的值變為1。接著又進入while循環的條件判斷,start<len依然為真。因此,又執行yield語句。但是由於start值為1,故而這一次next方法返回的值為1。

第3次next方法的調用類似。

當調用第4次next方法時,while循環的條件判斷start < len為假,while循環結束,MyGenerator方法調用也隨之結束,拋出StopIteration異常。

輸出結果

0  
1  
2  
Traceback (most recent call last):  
  File "test.py", line 21, in <module>  
    print gen.next()  
StopIteration 

有了上面的結果,重寫序列對象的叠代器輕而易舉。

def MyGenerator(sequence):  
    start  = 0  
    while start < len(sequence):  
        yield sequence[start]  
        start = start + 1  
  
  
gen = MyGenerator([1,2,3,a,b,c])  
  
for i in gen:  
    print i 

對比之前叠代器類的代碼,我們可以認識到,yield關鍵字為構造叠代器提供了多大的方便。它使得代碼長度縮減許多,同時也大大增強了可讀性。

三、生成器對象的send方法

生成器對象是一個叠代器。但是它比叠代器對象多了一些方法,它們包括send方法,throw方法和close方法。這些方法,主要是用於外部與生成器對象的交互。本文先介紹send方法。

send方法有一個參數,該參數指定的是上一次被掛起的yield語句的返回值。這樣說起來比較抽象,看下面的例子。

def MyGenerator():  
    value = (yield 1)  
    value = (yield value)  
  
  
gen = MyGenerator()  
print gen.next()  
print gen.send(2)  
print gen.send(3)

輸出的結果如下

1  
2  
Traceback (most recent call last):  
  File "test.py", line 18, in <module>  
    print gen.send(3)  
StopIteration  

上面代碼的運行過程如下。

當調用gen.next()方法時,Python首先會執行MyGenerator方法的yield 1語句。由於是一個yield語句,因此方法的執行過程被掛起,而next方法返回值為yield關鍵字後面表達式的值,即為1。

當調用gen.send(2)方法時,python首先恢復MyGenerator方法的運行環境。同時,將表達式(yield 1)的返回值定義為send方法參數的值,即為2。這樣,接下來value=(yield 1)這一賦值語句會將value的值置為2。繼續運行會遇到yield value語句。因此,MyGenerator方法再次被掛起。同時,send方法的返回值為yield關鍵字後面表達式的值,也即value的值,為2。

當調用send(3)方法時MyGenerator方法的運行環境。同時,將表達式(yield value)的返回值定義為send方法參數的值,即為3。這樣,接下來value=(yield value)這一賦值語句會將value的值置為3。繼續運行,MyGenerator方法執行完畢,故而拋出StopIteration異常。

總的來說,send方法和next方法唯一的區別是在執行send方法會首先把上一次掛起的yield語句的返回值通過參數設定,從而實現與生成器方法的交互。但是需要註意,在一個生成器對象沒有執行next方法之前,由於沒有yield語句被掛起,所以執行send方法會報錯。例如

gen = MyGenerator()  
print gen.send(2) 

上面代碼的輸出為

Traceback (most recent call last):  
  File "test.py", line 16, in <module>  
    print gen.send(2)  
TypeError: cant send non-None value to a just-started generator

當然,下面的代碼是可以接受的

gen = MyGenerator()  
print gen.send(None)

因為當send方法的參數為None時,它與next方法完全等價。但是註意,雖然上面的代碼可以接受,但是不規範。所以,在調用send方法之前,還是先調用一次next方法為好。

四、生成器對象的throw方法

上邊介紹的send方法,通過向生成器對象傳遞參數來實現與生成器對象的交互。本文介紹與生成器對象的另一種方式,即throw方法。它的實現手段是通過向生成器對象在上次被掛起處,拋出一個異常。之後會繼續執行生成器對象中後面的語句,直至遇到下一個yield語句返回。如果在生成器對象方法執行完畢後,依然沒有遇到yield語句,拋出StopIteration異常。

請看下面的例子

def myGenerator():  
    value = 1  
    while True:  
        yield value  
        value += 1  
  
  
gen = myGenerator()  
print gen.next()  
print gen.next()  
print gen.throw(Exception, "Method throw called!")  

輸出結果為

1  
2  
Traceback (most recent call last):  
  File "test.txt", line 11, in <module>  
    print gen.throw(Exception, "Method throw called!")  
  File "test.txt", line 4, in myGenerator  
    yield value  
Exception: Method throw called!  

代碼的最後一句向生成器對象拋出了一個異常。但是,在生成器對象的方法時沒有處理該異常的代碼,因此異常會被拋出到主方法。

下面的示例中,添加了處理異常的代碼

def myGenerator():  
    value = 1  
    while True:  
        try:  
            yield value  
            value += 1  
        except:  
            value = 1  
  
  
gen = myGenerator()  
print gen.next()  
print gen.next()  
print gen.throw(Exception, "Method throw called!")  

在上面的代碼中,加入了一個try-except語句塊處理異常。當生成器方法收到異常後,會調到except語句塊,將value置為1。因此,代碼的輸出如下。

1  
2  
1  
Exception RuntimeError: generator ignored GeneratorExit in <generator object myGenerator at 0x00000000028BB900> ignored

上面輸出中,第2個1是gen.throw方法的返回值。在執行完該方法後,生成器對象方法的while循環並沒有結束,也即是說生成器方法的執行還沒有結束。這個時候如果強制結束主程序,會拋出一個RuntimeError。也就是上面輸出的第4行。要優雅地關閉主程序,需要用到生成器對象的close方法。

五、GeneratorExit異常

當一個生成器對象被銷毀時,會拋出一個GeneratorExit異常。請看下面的代碼。

def myGenerator():    
    try:  
        yield 1  
    except GeneratorExit:  
        print "myGenerator exited"  
  
  
  
gen = myGenerator()  
print gen.next() 

輸出結果為

1  
myGenerator exited 

上面代碼的運行邏輯如下: 當調用到gen.next()方法時,會執行生成器對象方法的yield語句。此後,主程序結束,系統會自動產生一個GeneratorExit異常,被生成器對象方法的Except語句塊截獲。

而GeneratorExit異常產生的時機,是在生成器對象被銷毀之前。為了驗證這點,請看下面的代碼。

def myGenerator():    
    try:  
        yield 1  
        yield 2  
    except GeneratorExit:  
        print "myGenerator exited"  
  
  
  
gen = myGenerator()  
print gen.next()  
del gen  
print "Main caller exited" 

輸出結果

1  
myGenerator exited  
Main caller exited 

值得一提的是,GeneratorExit異常只有在生成器對象被激活後,才有可能產生。更確切的說,需要至少調用一次生成器對象的next方法後,系統才會產生GeneratorExit異常。請看下面的代碼。

def myGenerator():    
    try:  
        yield 1  
        yield 2  
    except GeneratorExit:  
        print "myGenerator exited"  
  
  
  
gen = myGenerator()  
del gen  
print "Main caller exited"  

其輸出結果如下:

Main caller exited  

在上面的示例中,我們都顯式地捕獲了GeneratorExit異常。如果該異常沒有被顯式捕獲,生成器對象也不會把該異常向主程序拋出。因為GeneratorExit異常定義的初衷,是方便開發者在生成器對象調用結束後定義一些收尾的工作,如釋放資源等。

六、生成器對象的close方法

生成器對象的close方法會在生成器對象方法的掛起處拋出一個GeneratorExit異常。GeneratorExit異常產生後,系統會繼續把生成器對象方法後續的代碼執行完畢。參見下面的代碼。

def myGenerator():    
    try:  
        yield 1  
        print "Statement after yield"  
    except GeneratorExit:  
        print "Generator error caught"  
  
    print "End of myGenerator"  
  
gen = myGenerator()  
print gen.next()  
gen.close()  
print "End of main caller"  

代碼執行過程如下:

  • 當調用gen.next方法時,會激活生成器,直至遇到生成器方法的yield語句,返回值1。同時,生成器方法的執行被掛起。
  • 當調用gen,close方法時,恢復生成器方法的執行過程。系統在yield語句處拋出GeneratorExit異常,執行過程跳到except語句塊。當except語句塊處理完畢後,系統會繼續往下執行,直至生成器方法執行結束。

代碼的輸出如下:

1  
Generator error caught  
End of myGenerator  
End of main caller

需要註意的是,GeneratorExit異常的產生意味著生成器對象的生命周期已經結束。因此,一旦產生了GeneratorExit異常,生成器方法後續執行的語句中,不能再有yield語句,否則會產生RuntimeError。請看下面的例子。

def myGenerator():    
    try:  
        yield 1  
        print "Statement after yield"  
    except GeneratorExit:  
        print "Generator error caught"  
  
    yield 3  
  
gen = myGenerator()  
print gen.next()  
gen.close()  
print "End of main caller"

輸出結果為

1  
Generator error caught  
Traceback (most recent call last):  
  File "test.txt", line 12, in <module>  
    gen.close()  
RuntimeError: generator ignored GeneratorExit

註意,由於RuntimError會向主方法拋出,因此主方法最後的print語句沒有執行。

有了上面的知識,我們就可以理解為什麽下面的代碼會拋出RuntimError錯誤了。

def myGenerator():    
    value = 1    
    while True:    
        try:    
            yield value    
            value += 1    
        except:    
            value = 1    
    
    
gen = myGenerator()    
print gen.next()    
print gen.next()    
print gen.throw(Exception, "Method throw called!") 

上面代碼中,當主程序結束前,系統產生GeneratorExit異常,被生成器對象方法的except語句捕獲,但是此時while語句還沒有退出,因此後面還會執行“yield value”這一語句,從而發生RuntimeError。要避免這個錯誤非常簡單,請看下面的代碼。

def myGenerator():    
    value = 1    
    while True:    
        try:    
            yield value    
            value += 1    
        except Exception:    
            value = 1    
    
    
gen = myGenerator()    
print gen.next()    
print gen.next()    
print gen.throw(Exception, "Method throw called!") 

代碼第7行的except語句聲明只捕獲Exception異常對象。這樣,當系統產生GeneratorExit異常後,不再被except語句捕獲,繼續向外拋出,從而跳出了生成器對象方法的while語句。

這裏再簡單說一句,GeneratorExit異常繼承自BaseException類。BaseException類與Exception類不同。一般情況下,BaseException類是所有內建異常類的基類,而Exception類是所有用戶定義的異常類的基類。

轉自:http://blog.csdn.net/hedan2013/article/details/72811117

python生成器(轉)