如果Python生成器不再使用但尚未达到StopIteration,那么它是否会被垃圾收集?

for*_*tyj 19 python garbage-collection generator coroutine

当不再使用发电机时,应该进行垃圾收集,对吧?我尝试了以下代码,但我不确定哪个部分我错了.

import weakref
import gc

def countdown(n):
    while n:
        yield n
        n-=1

cd = countdown(10)
cdw = weakref.ref(cd)()
print cd.next()
gc.collect()
print cd.next()
gc.collect()
print cdw.next()
Run Code Online (Sandbox Code Playgroud)

在最后一行,我打电话给垃圾收集器,因为没有再打电话cd了.gc应该是自由的cd.但是当我打电话时cdw.next(),它仍然打印8.我再试了几次cdw.next(),它可以成功打印所有其余的,直到StopIteration.

我试过这个是因为我想了解生成器和协同程序是如何工作的.在David Beazley的PyCon演讲"关于协同程序和并发的好奇课程"的幻灯片28中,他说一个协程可以无限期地运行,我们应该用.close()它来关闭它.然后他说垃圾收集器会打电话.close().根据我的理解,一旦我们打电话给.close()自己,gc就会.close()再次打电话.会gc收到警告说它无法调用.close()已经关闭的协程吗?

谢谢你的任何投入.

mgi*_*son 10

由于python的动态特性,在cd你到达当前例程的末尾之前不会释放引用,因为(至少)python的Cpython实现没有"预读".(如果你不知道你正在使用什么python实现,那几乎肯定是"Cpython").如果一个对象在一般情况下仍然存在于当前命名空间中(例如,您仍然可以通过调用到达它locals()),那么解释器几乎不可能确定对象是否应该是空闲的.

在一些不太常见的情况下,其他python实现可能能够在当前堆栈帧结束之前释放对象,但Cpython不会打扰.

试试这段代码,证明生成器可以在Cpython中自由清理:

import weakref
def countdown(n):
    while n:
        yield n
        n-=1

def func():
    a = countdown(10)
    b = weakref.ref(a)
    print next(a)
    print next(a)
    return b

c = func()
print c()
Run Code Online (Sandbox Code Playgroud)

当引用计数达到0时,对象(包括生成器)被垃圾收集(在Cpython中 - 其他实现可能以不同的方式工作).在Cpython中,引用计数仅在您看到del语句时或者由于当前命名空间更改而导致对象超出范围时递减.

重要的是,一旦没有对象的引用,垃圾收集器就可以自由清理它.实现如何确定没有更多引用的详细信息留给您正在使用的特定python发行版的实现者.

  • 它处理它处理所有其他动态怪异的方式(奇怪的类型弹出,替换方法或类或模块,摆弄`__dict__`等):它从优化的代码中解脱出来,做低效的事情(有时甚至是比CPython做的更令人沮丧,从它本已经完成的事情开始(例如,分配中间对象),如果它根本没有优化的话.这是跟踪JIT编译器101.您认为PyPy如何设法优化*任何*? (2认同)

Ben*_*son 8

在您的示例中,生成器在脚本结束之前不会收集垃圾.Python不知道你是否会cd再次使用它,所以它不能扔掉它.准确地说,在全局命名空间中仍然存在生成器的引用.

引用计数降至零时,生成器将获得GCed ,就像任何其他对象一样.即使发电机没有耗尽.

这可能发生在许多正常情况下 - 如果它的本地名称超出范围,如果它是deled,如果它的所有者获得GCed.但是,如果任何活动对象(包括名称空间)拥有对它的强引用,它将不会获得GCed.