[英]Python : Behaviour of send() in generators
我在 python 3 中試驗生成器並編寫了這個相當人為的生成器:
def send_gen():
print(" send_gen(): will yield 1")
x = yield 1
print(" send_gen(): sent in '{}'".format(x))
# yield # causes StopIteration when left out
gen = send_gen()
print("yielded {}".format(gen.__next__()))
print("running gen.send()")
gen.send("a string")
輸出:
send_gen(): will yield 1
yielded 1
running gen.send()
send_gen(): sent in 'a string'
Traceback (most recent call last):
File "gen_test.py", line 12, in <module>
gen.send("a string")
StopIteration
所以gen.__next__()
到達行x = yield 1
並產生 1。我認為x
將被分配給None
,然后gen.send()
將尋找下一個yield
語句,因為x = yield 1
被“使用”,然后得到一個StopIteration
。
相反,什么似乎已經發生的是, x
被發送“字符串”,這是印刷,然后再蟒蛇試圖尋找下一個yield
,並得到一個StopIteration
。
所以我試試這個:
def send_gen():
x = yield 1
print(" send_gen(): sent in '{}'".format(x))
gen = send_gen()
print("yielded : {}".format(gen.send(None)))
輸出 :
yielded : 1
但現在沒有錯誤。 在將x
分配給None
后, send()
似乎沒有嘗試尋找下一個yield
語句。
為什么行為略有不同? 這與我如何啟動發電機有關嗎?
行為沒有區別; 在第二次設置中,你永遠不會超越生成器中的第一個yield
表達式。 請注意, StopIteration
不是錯誤 ; 這是正常行為,每當發電機結束時就會發出預期的信號。 在您的第二個示例中,您只是從未到達生成器的末尾。
當發電機達到yield
的表情,執行暫停在那里 ,直到它被重新表達不能發生器內產生任何結果。 gen.__next__()
或gen.send()
都將從該點恢復執行, yield
表達式生成gen.send()
傳入的值或None
。 如果有幫助的話,你可以看gen.__next__()
作為gen.send(None)
。 在這里有一點要明白的是, gen.send()
具有yield
第一返回發送的值, 然后發電機繼續到下一個yield
。
因此,給出第一個示例生成器,會發生這種情況:
gen = send_gen()
創建生成器對象。 代碼暫停在函數的最頂層,沒有執行任何操作。
你可以調用gen.__next__()
或gen.send(None)
; 生成器開始並執行,直到第一個yield
表達式:
print(" send_gen(): will yield 1") yield 1
現在暫停執行。 gen.__next__()
或gen.send(None)
調用現在返回1
,由yield 1
產生的值。 因為現在暫停了發生器,所以x = ...
賦值還不能發生! 這只會在發電機再次恢復時發生。
你在第一個例子中調用gen.send("a string")
,不要在第二個例子中進行任何調用。 因此,對於第一個示例,現在恢復生成器函數:
x = <return value of the yield expression> # 'a string' in this case print(" send_gen(): sent in '{}'".format(x))
現在函數結束了 ,所以引發了StopIteration
。
因為您在第二個示例中從未恢復生成器,所以未到達生成器的末尾並且不會引發StopIteration
異常。
請注意,因為生成器從函數的頂部開始,所以在該點沒有yield
表達式返回您使用gen.send()
發送的任何內容,因此第一個gen.send()
值必須始終為None
或引發異常。 最好是使用一個明確的gen.__next__()
或者說一個next(gen)
函數調用),以“素”發電機所以它會在第一次暫停yield
表達。
這里的關鍵區別在於,你已經打在你的第一個例子中,發電機的兩倍 ,但你打發電機在第二個例子中只有一次 。
當您定義一個協同程序 ,即您打算將參數發送到的生成器時,您必須事先通過前進到第一個yield語句來“填充”它。 只有這樣你才能發送價值觀。 在第一個示例中,您在嘗試send
之前通過調用gen.__next__()
顯式地完成了此操作。
在第二個例子中,您還通過執行gen.send(None)
來gen.send(None)
它(請注意,在None
中發送實際上相當於調用gen.__next__()
或next(gen)
)。 但是你沒有嘗試第二次發送一個值,所以在這種情況下沒有StopIteration
。 發電機只是坐在那里停留在yield聲明等待你再次擊中它,這也是你之后還沒有看到打印的原因。
另一點需要注意的是,如果您在第二個示例中發送了除None
之外的任何內容,則會出現錯誤:
TypeError: can't send non-None value to a just-started generator
這就是我所說的'啟動'協程。
這在技術上是一個協程而不是一個生成器:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.