這個問題讓我拉我的頭髮。Python生成器如何知道誰在調用?
如果我這樣做:
def mygen():
for i in range(100):
yield i
,並從此一干線程調用它,如何發電機知道接下來的每個線程發送? 我每次調用它時,生成器是否會保存一個帶有計數器和調用者引用的表格?
這很奇怪。
請澄清我的想法。
這個問題讓我拉我的頭髮。Python生成器如何知道誰在調用?
如果我這樣做:
def mygen():
for i in range(100):
yield i
,並從此一干線程調用它,如何發電機知道接下來的每個線程發送? 我每次調用它時,生成器是否會保存一個帶有計數器和調用者引用的表格?
這很奇怪。
請澄清我的想法。
mygen
不需要記住任何東西。每次調用mygen()
都會返回一個獨立的迭代。這些iterables,在另一方面,有狀態:每次next()
叫上一個,就跳轉到正確的位置在發電機碼 - 遇到yield
時,控制交回給調用者。實際執行是相當混亂,但原則上,你可以想像,這樣一個迭代器存儲局部變量,字節碼,並在字節碼的當前位置(又名指令指針)。這裏的線程沒有什麼特別之處。
這樣的函數在調用時會返回一個生成器對象。如果您在同一個生成器對象上有單獨的線程調用next()
,它們將互相干擾。也就是說,5個線程每次調用next()
10次將獲得50個不同的收益。
如果兩個線程都通過在線程中調用mygen()
來創建生成器,它們將具有單獨的生成器對象。
發電機是一個對象,並且其狀態將被存儲在內存中,所以兩個線程,每個創建一個mygen()
將指單獨的對象。它與創建class
中的對象的兩個線程沒有區別,即使類相同,它們也會有不同的對象。
如果你是從C背景來的,這是而不是變量與具有static
變量的函數相同。狀態保持在一個對象中,而不是靜態地包含在函數中的變量中。
你沒有完全回答我的問題。我不想知道會發生什麼,但它是如何發生的。當線程創建生成器時,生成器是否在內存中保留一些值? – 2013-05-05 21:04:11
@PatrickBassut生成器對象的狀態與其他對象一樣,所以是的,它的狀態將被存儲在內存中。 – 2013-05-05 21:07:15
如果你這樣看,它可能會更清晰。相反的:
for i in mygen():
. . .
使用:
gen_obj = mygen()
for i in gen_obj:
. . .
,那麼你可以看到mygen()只調用一次,它創建一個新的對象,而且它是被迭代該對象。如果需要,可以在同一個線程中創建兩個序列:
gen1 = mygen()
gen2 = mygen()
print(gen1.__next__(), gen2.__next__(), gen1.__next__(), gen2.__next__())
這將打印0,0,1,1。
你可以從兩個線程訪問相同的迭代器,如果你喜歡,只是存儲生成器對象在全局:
global_gen = mygen()
主題1:
for i in global_gen:
. . .
線程2:
for i in global_gen:
. . .
這可能會導致各種破壞。 :-)
發電機有點奇怪。您可以將它們分配給變量,但在您實際使用它們時,它會以某種「按需」方式開始生成值。如果你指定了一個函數的內存位置,那就完全容易理解。但據我所知,你沒有那樣做(你實際上在gen_obj = mygen()中調用函數)。哇! – 2013-05-06 04:48:41
當您前進到嚴重的Python-Fu的下一個級別時,可以將指針綁定到__next __()方法作爲GUI回調。 :-) – 2013-05-06 04:59:22
大多數情況下,「def」語句會從您的代碼中創建一個函數對象,當您按名稱調用函數時會執行該對象。但是,如果代碼包含「yield」,def會創建* 2個函數對象:調用函數名稱時被調用的對象根本沒有任何代碼;它只是返回生成器對象。它通過代碼創建的函數對象通過該生成器對象的__next__屬性進行調用,該函數將其狀態保存在生成器對象中,並知道如何在調用之間進行保存和恢復。 – 2013-05-06 05:40:13
是的,線程只是爲了說明問題。考慮到生成器可能會給Python初學者帶來錯誤的併發性(或者更多的黑魔法)。 – 2013-05-05 21:12:12
@PatrickBassut:那麼,你可以用它們模擬[coroutines](https://en.wikipedia.org/wiki/Coroutine),並且可以使用協程[green threads](https://en.wikipedia.org /維基/ Green_threads)。 – icktoofay 2013-05-05 21:20:56