簡體   English   中英

Python:生成器中 send() 的行為

[英]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

因此,給出第一個示例生成器,會發生這種情況:

  1. gen = send_gen()創建生成器對象。 代碼暫停在函數的最頂層,沒有執行任何操作。

  2. 你可以調用gen.__next__()gen.send(None) ; 生成器開始並執行,直到第一個yield表達式:

     print(" send_gen(): will yield 1") yield 1 

    現在暫停執行。 gen.__next__()gen.send(None)調用現在返回1 ,由yield 1產生的值。 因為現在暫停了發生器,所以x = ...賦值還不能發生! 這只會在發電機再次恢復時發生。

  3. 你在第一個例子中調用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

這就是我所說的'啟動'協程。

這在技術上是一個協程而不是一個生成器:

  1. 當我們調用send_gen()時,協程對象被創建
  2. 當 gen.___next____() 被調用 [ 我們應該使用 next(gen) ] 時,生成器函數被調用,它返回 1 並阻塞
  3. 當調用 gen.send("a string") 時,協程喚醒並處理輸入(此處打印“a string”)
  4. 然后協程退出

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM