Python生成器(send,close,throw)方法詳解

2020-07-16 10:05:20
《Python生成器》一節中,詳細介紹了如何建立一個生成器,以及生成器的基礎用法。本節將在其基礎上,繼續講解和生成器有關的一些方法。

Python生成器send()方法

我們知道,通過呼叫 next() 或者 __next__() 方法,可以實現從外界控制生成器的執行。除此之外,通過 send() 方法,還可以向生成器中傳值。

值得一提的是,send() 方法可帶一個引數,也可以不帶任何引數(用 None 表示)。其中,當使用不帶引數的 send() 方法時,它和 next() 函數的功能完全相同。例如:
def intNum():
    print("開始執行")
    for i in range(5):
        yield i
        print("繼續執行")
num = intNum()
print(num.send(None))
print(num.send(None))
程式執行結果為:

開始執行
0
繼續執行
1

注意,雖然 send(None) 的功能是 next() 完全相同,但更推薦使用 next(),不推薦使用 send(None)。

這裡重點講解一些帶引數的 send(value) 的用法,其具備 next() 函數的部分功能,即將暫停在 yield 語句出的程式繼續執行,但與此同時,該函數還會將 value 值作為 yield 語句返回值賦值給接收者。

注意,帶引數的 send(value) 無法啟動執行生成器函數。也就是說,程式中第一次使用生成器呼叫 next() 或者 send() 函數時,不能使用帶引數的 send() 函數。

舉個例子:
def foo():
    bar_a = yield "hello"
    bar_b = yield bar_a
    yield bar_b

f = foo()
print(f.send(None))
print(f.send("C語言中文網"))
print(f.send("http://c.biancheng.net"))
分析一下此程式的執行流程:
1) 首先,構建生成器函數,並利用器建立生成器(物件)f 。

2) 使用生成器 f 呼叫無參的 send() 函數,其功能和 next() 函數完全相同,因此開始執行生成器函數,即執行到第一個 yield "hello" 語句,該語句會返回 "hello" 字串,然後程式停止到此處(注意,此時還未執行對 bar_a 的賦值操作)。

3) 下面開始使用生成器 f 呼叫有參的 send() 函數,首先它會將暫停的程式開啟,同時還會將其引數“C語言中文網”賦值給當前 yield 語句的接收者,也就是 bar_a 變數。程式一直執行完 yield bar_a 再次暫停,因此會輸出“C語言中文網”。

4) 最後依舊是呼叫有參的 send() 函數,同樣它會啟動餐廳的程式,同時將引數“http://c.biancheng.net”傳給 bar_b,然後執行完 yield bar_b 後(輸出 http://c.biancheng.net),程式執行再次暫停。

因此,該程式的執行結果為:

hello
C語言中文網
http://c.biancheng.net

Python生成器close()方法

當程式在生成器函數中遇到 yield 語句暫停執行時,此時如果呼叫 close() 方法,會阻止生成器函數繼續執行,該函數會在程式停止執行的位置丟擲 GeneratorExit 異常。

舉個例子:
def foo():
    try:
        yield 1
    except GeneratorExit:
        print('捕獲到 GeneratorExit')
f = foo()
print(next(f))
f.close()
程式執行結果為:

1
捕獲到 GeneratorExit


注意,雖然通過捕獲 GeneratorExit 異常,可以繼續執行生成器函數中剩餘的程式碼,帶這部分程式碼中不能再包含 yield 語句,否則程式會丟擲 RuntimeError 異常。例如:
def foo():
    try:
        yield 1
    except GeneratorExit:
        print('捕獲到 GeneratorExit')
        yield 2 #丟擲 RuntimeError 異常

f = foo()
print(next(f))
f.close()
程式執行結果為:

1
捕獲到 GeneratorExit Traceback (most recent call last):
  File "D:python3.61.py", line 10, in <module>
    f.close()
RuntimeError: generator ignored GeneratorExit


另外,生成器函數一旦使用 close() 函數停止執行,後續將無法再呼叫 next() 函數或者 __next__() 方法啟動執行,否則會丟擲 StopIteration 異常。例如:
def foo():
    yield "c.biancheng.net"
    print("生成器停止執行")

f = foo()
print(next(f)) #輸出 "c.biancheng.net"
f.close()
next(f) #原本應輸出"生成器停止執行"
程式執行結果為:

c.biancheng.net
Traceback (most recent call last):
  File "D:python3.61.py", line 8, in <module>
    next(f) #原本應輸出"生成器停止執行"
StopIteration

Python生成器throw()方法

生成器 throw() 方法的功能是,在生成器函數執行暫停處,丟擲一個指定的異常,之後程式會繼續執行生成器函數中後續的程式碼,直到遇到下一個 yield 語句。需要注意的是,如果到剩餘程式碼執行完畢沒有遇到下一個 yield 語句,則程式會丟擲 StopIteration 異常。

舉個例子:
def foo():
    try:
        yield 1
    except ValueError:
        print('捕獲到 ValueError')

f = foo()
print(next(f))
f.throw(ValueError)
程式執行結果為:

1
捕獲到 ValueError
Traceback (most recent call last):
  File "D:python3.61.py", line 9, in <module>
    f.throw(ValueError)
StopIteration

顯然,一開始生成器函數在 yield 1 處暫停執行,當執行 throw() 方法時,它會先丟擲 ValueError 異常,然後繼續執行後續程式碼找到下一個 yield 語句,該程式中由於後續不再有 yield 語句,因此程式執行到最後,會丟擲一個 StopIteration 異常。