簡體   English   中英

Python 3:生成器的發送方法

[英]Python 3: send method of generators

我無法理解send方法。 我知道它是用來操作發電機的。 但語法在這里: generator.send(value)

我以某種方式無法理解為什么該值應該成為當前yield表達式的結果。 我准備了一個例子:

def gen():
    for i in range(10):
        X = yield i
        if X == 'stop':
            break
        print("Inside the function " + str(X))

m = gen()
print("1 Outside the function " + str(next(m)) + '\n')
print("2 Outside the function " + str(next(m)) + '\n')
print("3 Outside the function " + str(next(m)) + '\n')
print("4 Outside the function " + str(next(m)) + '\n')
print('\n')
print("Outside the function " + str(m.send(None)) + '\n') # Start generator
print("Outside the function " + str(m.send(77)) + '\n')
print("Outside the function " + str(m.send(88)) + '\n')
#print("Outside the function " + str(m.send('stop')) + '\n')
print("Outside the function " + str(m.send(99)) + '\n')
print("Outside the function " + str(m.send(None)) + '\n')

結果是:

1 Outside the function 0

Inside the function None
2 Outside the function 1

Inside the function None
3 Outside the function 2

Inside the function None
4 Outside the function 3



Inside the function None
Outside the function 4

Inside the function 77
Outside the function 5

Inside the function 88
Outside the function 6

Inside the function 99
Outside the function 7

Inside the function None
Outside the function 8

好吧,坦率地說,這讓我感到驚訝。

  1. 在文檔中我們可以讀到,當執行yield語句時,生成器的狀態被凍結,並且expression_list的值返回給next的調用者。 好吧,它似乎沒有發生。 為什么我們可以在gen()執行if語句和print函數。
  2. 我如何理解為什么函數內外的X不同? 行。 讓我們假設send(77)傳輸到m 那么, yield表達式變成了 77。那么X = yield i什么? 函數內部的 77 在外部發生時如何轉換為 5?
  3. 為什么第一個結果字符串不反映生成器內部發生的任何事情?

不管怎樣,你能不能以某種方式評論這些sendyield語句?

當您在生成器中使用send和 expression yield時,您將其視為協程; 一個單獨的執行線程,可以順序交錯但不能與其調用者並行運行。

當調用者執行R = m.send(a) ,它將對象a放入生成器的輸入槽中,將控制權轉移到生成器,並等待響應。 生成器接收對象a作為X = yield i的結果,並運行直到遇到另一個yield表達式,例如Y = yield j 然后它將j放入其輸出槽,將控制權轉回給調用者,並等待它再次恢復。 調用者接收j作為R = m.send(a) ,並運行直到遇到另一個S = m.send(b)語句,依此類推。

R = next(m)R = m.send(None) 它將None放入生成器的輸入槽中,因此如果生成器檢查X = yield i的結果,則X將為None

作為一個比喻,考慮一個愚蠢的服務員

啞巴服務員

當服務員接到客戶的訂單時,他們將墊子放在啞服務員中, send其送到廚房,然后在艙口旁等待菜:

R = kitchen.send("Ham omelette, side salad")

廚師(誰一直在等待通過艙口)拿起順序,准備的菜, yield是S到餐廳,並為下一個訂單等待:

next_order = yield [HamOmelette(), SideSalad()]

服務員(一直在門口等着)把菜拿給顧客,然后帶着另一個訂單回來,等等。

因為服務器和廚師在send訂單或yield出一道菜后都在艙口等待,所以在任何時候都只有一個人在做任何事情,即該過程是單線程的。 雙方都可以使用正常的控制流程,因為生成器機器(啞服務員)負責交錯執行。

最令人困惑的部分應該是這一行X = yield i ,特別是當您在生成器上調用send()時。 其實你唯一需要知道的是:

在詞匯層面: next()等於send(None)

在解釋器級別: X = yield i等於以下行( ORDER MATTERS ):

yield i
# won't continue until next() or send() is called
# and this is also the entry point of next() or send()
X = the_input_of_send

並且,注釋的 2 行正是我們第一次需要調用send(None)的確切原因,因為生成器會將值分配給X之前返回i (yield i )

def gen():
    i = 1
    while True:
        i += 1
        x = yield i
        print(x)

m = gen()
next(m)
next(m)
m.send(4)

結果

None
4

查看上面更簡化的代碼。
我認為導致您混淆的事情是“x = yield i”語句,該語句並不是說從 send() 方法接受的值分配給 i 然后我分配給 x。 相反,值 i 由 yield 語句返回給生成器,x 由 send() 方法分配。一個語句同時做兩件事。

由於您甚至要求發表評論,請考慮以下情況:

def lambda_maker():
    def generator():
        value = None
        while 1:
            value = yield value
            value= value[0][1]
    f = generator()
    next(f)  # skip the first None
    return f.send  # a handy lambda value: value[0][1]

現在以下兩行是等效的:

a_list.sort(key=lambda a: a[0][1])
a_list.sort(key=lambda_maker())

(順便說一句,在當前(2018 年 5 月 26 日,GDPR 后第 1 天)CPython2 和 CPython3 實現中,第二行比第一行運行得更快,但這是與每個函數調用的框架對象初始化開銷相關的細節。)

這里會發生什么? lambda_maker調用f=generator()並獲得一個生成器; 調用初始next(f)開始運行生成器並消耗初始None值,並在yield線處暫停。 然后它將綁定的方法f.send返回給它的調用者。 從這以后,每到這個點這個綁定的方法被調用時, generator.value本地接收綁定的方法的參數,重新計算value ,然后循環返回yield荷蘭國際集團的當前值value ,並等待下一個.send再弄值.

生成器對象保留在內存中,它在循環中所做的一切是:

  • 產生當前結果(最初為無)
  • 接收另一個值(無論有人用作.send參數)
  • 根據接收到的值重新計算當前結果
  • 環回

筆記:
為簡單起見,我的回答僅限於生成器每行最多有 1 個yield命令的情況。

特爾;博士:

  • .send()方法:

    • 當前掛起的命令發送一個值,但是
    • 下一個即將到來的掛起命令接收一個值
  • 發送值是“接收” yield 表達式本身。
    這意味着表達式yield 7

    • 產生值7 ,但
    • 自己的值,即(yield 7)的值,可以是例如"hello"
      (括號通常是強制性的,除了最簡單的情況。)

詳細地:

前言:

g = gen()是生成器迭代器gen().一個實例gen().

  • 命令next(g)行為與g.send(None)完全一樣。

    • 所以在接下來的解釋中,我將完全省略next()函數。
  • 僅當實例g在帶有yield命令的語句處掛起時,才允許發送None
    在此處輸入圖片說明

    • 為什么? 因為.send()方法直接向掛起的 yield 表達式發送一個值(請參閱下面“逐步”部分中的第4.點)。

    所以在發送之前不可─ None價值,我們必須通過向它把這個生成器實例這樣的掛起狀態None價值。 它可能像g.send(None)一樣簡單:

    在此處輸入圖片說明

  • 但就在g掛起之前,它產生了yield命令的值。 這個產生的值成為.send()方法的返回值:

    在此處輸入圖片說明

    我們可能想使用這個接收到的值或將它保存在一個變量中以備后用,所以讓我們開始我們的旅程,而不是前兩張圖片:


一步步:

  1. 第一個.send()啟動實例g 實例g開始執行它的命令,直到第一個 yield 語句,它產生它的值:

    在此處輸入圖片說明

    這意味着,在變量from_iterator_1中將是字符串"first_from_iterator"

  2. 現在,在產生它的第一個值之后,我們有g處於掛起狀態

    在此處輸入圖片說明

    這允許我們向g發送一些有用的東西,除了None — 例如數字1

  3. 所以讓我們將數字1發送到g 在此處輸入圖片說明

  4. 由於g表達式yield "first_from_iterator"處暫停,因此該表達式(本身)的值將變為1

    (是的, yield "first_from_iterator"一個表達式,同樣a + b是。)

    回想一下,此時值"first_from_iterator"很久以前就已經產生了。

  5. 實例g然后喚醒,並且——反過來—— g.send()現在等待返回值。 在此處輸入圖片說明

  6. 將執行先前暫停,現在喚醒的語句。
    (在暫停之前,沒有執行,它只產生了一個值。 在此處輸入圖片說明 在我們的簡單例子中(被喚醒的語句是yield "first_from_iterator" )還有什么要執行的,但是呢

    • 將發送的值 ( 1 ) 保存到變量以供以后使用?

       received_1 = yield "first_from_iterator"
    • 或者用它執行更復雜的計算?

       result = math.cos((yield "first_from_iterator") * math.pi) # result: -1

  7. g所有后續語句都將執行,但只執行到包含yield命令的下一條語句。

    在此處輸入圖片說明

  8. 一條語句(包含yield命令)產生一個值

    在此處輸入圖片說明

    它再次掛起g並喚醒等待的.send()方法(通過為其提供預期的 - 產生的 - 返回值)。

  9. 它允許在它之后執行下一個命令:

    在此處輸入圖片說明

  10. 現在我們處於與第 2 點相同的情況。 — 就在執行 (next) .send()方法之前 — 所以故事將重復

    筆記:
    它將以上面“前言”部分的最后一點相同的問題重復出現 - 我們可能不想丟棄產生的值,因此而不是命令

    g.send(1) # Not very appropriate

    最好使用一些東西作為

    from_iterator_2 = g.send(1) # Saving the 2nd yielded value

    (對於下一個g.send(2)命令也類似)。


大圖:

(第一次send (帶有None參數)啟動生成器實例執行其命令。)

在此處輸入圖片說明

暫無
暫無

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

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