2013-05-05 24 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