2013-05-05 67 views
6

這個問題讓我拉我的頭髮。Python生成器如何知道誰在調用?

如果我這樣做:

def mygen(): 
    for i in range(100): 
     yield i 

,並從此一干線程調用它,如何發電機知道接下來的每個線程發送? 我每次調用它時,生成器是否會保存一個帶有計數器和調用者引用的表格?

這很奇怪。

請澄清我的想法。

回答

6

mygen不需要記住任何東西。每次調用mygen()都會返回一個獨立的迭代。這些iterables,在另一方面,有狀態:每次next()叫上一個,就跳轉到正確的位置在發電機碼 - 遇到yield時,控制交回給調用者。實際執行是相當混亂,但原則上,你可以想像,這樣一個迭代器存儲局部變量,字節碼,並在字節碼的當前位置(又名指令指針)。這裏的線程沒有什麼特別之處。

+0

是的,線程只是爲了說明問題。考慮到生成器可能會給Python初學者帶來錯誤的併發性(或者更多的黑魔法)。 – 2013-05-05 21:12:12

+0

@PatrickBassut:那麼,你可以用它們模擬[coroutines](https://en.wikipedia.org/wiki/Coroutine),並且可以使用協程[green threads](https://en.wikipedia.org /維基/ Green_threads)。 – icktoofay 2013-05-05 21:20:56

2

這樣的函數在調用時會返回一個生成器對象。如果您在同一個生成器對象上有單獨的線程調用next(),它們將互相干擾。也就是說,5個線程每次調用next() 10次將獲得50個不同的收益。

如果兩個線程都通過在線程中調用mygen()來創建生成器,它們將具有單獨的生成器對象。

發電機是一個對象,並且其狀態將被存儲在內存中,所以兩個線程,每個創建一個mygen()將指單獨的對象。它與創建class中的對象的兩個線程沒有區別,即使類相同,它們也會有不同的對象。

如果你是從C背景來的,這是而不是變量與具有static變量的函數相同。狀態保持在一個對象中,而不是靜態地包含在函數中的變量中。

+0

你沒有完全回答我的問題。我不想知道會發生什麼,但它是如何發生的。當線程創建生成器時,生成器是否在內存中保留一些值? – 2013-05-05 21:04:11

+4

@PatrickBassut生成器對象的狀態與其他對象一樣,所以是的,它的狀態將被存儲在內存中。 – 2013-05-05 21:07:15

1

如果你這樣看,它可能會更清晰。相反的:

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: 
    . . . 

這可能會導致各種破壞。 :-)

+0

發電機有點奇怪。您可以將它們分配給變量,但在您實際使用它們時,它會以某種「按需」方式開始生成值。如果你指定了一個函數的內存位置,那就完全容易理解。但據我所知,你沒有那樣做(你實際上在gen_obj = mygen()中調用函數)。哇! – 2013-05-06 04:48:41

+1

當您前進到嚴重的Python-Fu的下一個級別時,可以將指針綁定到__next __()方法作爲GUI回調。 :-) – 2013-05-06 04:59:22

+1

大多數情況下,「def」語句會從您的代碼中創建一個函數對象,當您按名稱調用函數時會執行該對象。但是,如果代碼包含「yield」,def會創建* 2個函數對象:調用函數名稱時被調用的對象根本沒有任何代碼;它只是返回生成器對象。它通過代碼創建的函數對象通過該生成器對象的__next__屬性進行調用,該函數將其狀態保存在生成​​器對象中,並知道如何在調用之間進行保存和恢復。 – 2013-05-06 05:40:13