[英]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
好吧,坦率地說,這讓我感到驚訝。
yield
語句時,生成器的狀態被凍結,並且expression_list
的值返回給next
的調用者。 好吧,它似乎沒有發生。 為什么我們可以在gen()
執行if
語句和print
函數。X
不同? 行。 讓我們假設send(77)
傳輸到m
。 那么, yield
表達式變成了 77。那么X = yield i
什么? 函數內部的 77 在外部發生時如何轉換為 5? 不管怎樣,你能不能以某種方式評論這些send
和yield
語句?
當您在生成器中使用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()
方法的返回值:
我們可能想使用這個接收到的值或將它保存在一個變量中以備后用,所以讓我們開始我們的旅程,而不是前兩張圖片:
一步步:
第一個.send()
啟動實例g
。 實例g
開始執行它的命令,直到第一個 yield 語句,它產生它的值:
這意味着,在變量from_iterator_1
中將是字符串"first_from_iterator"
。
現在,在產生它的第一個值之后,我們有g
處於掛起狀態
這允許我們向g
發送一些有用的東西,除了None
— 例如數字1
。
由於g
在表達式yield "first_from_iterator"
處暫停,因此該表達式(本身)的值將變為1
。
(是的, yield "first_from_iterator"
是一個表達式,同樣a + b
是。)
回想一下,此時值"first_from_iterator"
很久以前就已經產生了。
將執行先前暫停,現在喚醒的語句。
(在暫停之前,沒有執行,它只產生了一個值。 ) 在我們的簡單例子中(被喚醒的語句是yield "first_from_iterator"
)還有什么要執行的,但是呢
將發送的值 ( 1
) 保存到變量以供以后使用?
received_1 = yield "first_from_iterator"
或者用它執行更復雜的計算?
result = math.cos((yield "first_from_iterator") * math.pi) # result: -1
g
所有后續語句都將執行,但只執行到包含yield
命令的下一條語句。
下一條語句(包含yield
命令)產生一個值
它再次掛起g
並喚醒等待的.send()
方法(通過為其提供預期的 - 產生的 - 返回值)。
它允許在它之后執行下一個命令:
現在我們處於與第 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.