这个问题让我拉我的头发。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