如果Python生成器不再使用但还没有达到StopIteration,它会被垃圾回收吗?

2024-03-29 04:56:38 发布

您现在位置:Python中文网/ 问答频道 /正文

当发电机不再使用时,它应该被垃圾收集,对吗?我尝试了下面的代码,但我不确定我错了哪一部分。在

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()

在最后第二行,我调用了垃圾回收器,因为不再调用cdgc应该释放cd对。但当我调用cdw.next()时,它仍在打印8。我又试了几个cdw.next(),它可以成功地打印出其余的,直到停止迭代。在

我尝试这个是因为我想了解生成器和协同程序是如何工作的。在davidbeazley的PyCon演示“关于协同程序和并发性的奇怪课程”的幻灯片28中,他说协同程序可能会无限期运行,我们应该使用.close()来关闭它。然后他说垃圾收集器将调用.close()。在我的理解中,一旦我们自己调用了.close()gc就会再次调用{}。gc是否会收到一条警告,说明它不能在已关闭的协同程序上调用.close()?在

谢谢你的意见。在


Tags: 代码importclosedefcdgc发电机垃圾
3条回答

Python垃圾回收器并不那么聪明。即使在那一行之后不再引用cd,但引用仍然存在于局部变量中,因此无法收集它。(事实上,您正在使用的一些代码可能会在您的局部变量中挖掘并重新生成它。不太可能,但可能。所以Python不能做任何假设。)

如果要让垃圾回收器在此处实际执行某些操作,请尝试添加:

del cd

这将删除局部变量,从而允许收集对象。在

由于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()

当对象(包括生成器)的引用计数达到0时(在Cpython中——其他实现的工作方式可能不同)将被垃圾回收。{cpythys>只有当一个对象的引用^减少时,{cpyth才算是引用。在

重要的是,一旦不再引用对象,就可以由垃圾回收器清理。关于实现如何确定不再有引用的细节将留给您正在使用的特定python发行版的实现者。在

在您的示例中,直到脚本结束,生成器才会进行垃圾回收。Python不知道您是否要再次使用cd,因此它不能丢弃它。准确地说,在全局名称空间中仍然存在对生成器的引用。在

当生成器的引用计数(reference count)降至零时,生成器将获得GCed,就像任何其他对象一样。即使发电机没有耗尽。在

这种情况在很多正常情况下都会发生——如果它在超出范围的本地名称中,如果它是deled,如果它的所有者得到GCed。但是如果任何活动对象(包括名称空间)包含对它的强引用,它就不会得到GCed。在

相关问题 更多 >