当不再使用发电机时,应该进行垃圾收集,对吧?我尝试了以下代码,但我不确定哪个部分我错了。
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()
在倒数第二行,我调用了垃圾收集器,因为不再调用cd
。 gc
应免费cd
。但是当我打电话给cdw.next()
时,它仍然打印8.我再试了几次cdw.next()
,它可以成功打印所有其余的直到StopIteration。
我试过这个,因为我想了解生成器和协程如何工作。在David Beazley的PyCon演讲“关于协同程序和并发的好奇课程”的幻灯片28中,他说一个协程可以无限期地运行,我们应该使用.close()
来关闭它。然后他说垃圾收集器会拨打.close()
。根据我的理解,一旦我们自己致电.close()
,gc
将再次致电.close()
。 gc
会收到一条警告,指出它无法在已经关闭的协程上调用.close()
吗?
感谢您的任何意见。
答案 0 :(得分:10)
由于python的动态特性,在到达当前例程的末尾之前不会释放对cd
的引用,因为(至少)python的Cpython实现没有“预读”。 (如果你不知道你正在使用什么python实现,那几乎肯定是“Cpython”)。如果一个对象在一般情况下仍然存在于当前命名空间中,那么解释器几乎不可能确定某个对象是否应该是空闲的(例如,您仍然可以通过调用{{1}来访问它})。
在一些不太常见的情况下,其他python实现可能能够在当前堆栈帧结束之前释放对象,但Cpython不会打扰。
尝试使用此代码,该代码演示了可以在Cpython中自由清理生成器:
locals()
当引用计数达到0时,对象(包括生成器)被垃圾收集(在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()
语句时或者由于当前名称空间更改而导致对象超出范围时递减。
重要的是,一旦没有对象的引用,垃圾收集器就可以自由清理它。有关如何确定没有更多引用的详细信息留给您正在使用的特定python发行版的实现者。
答案 1 :(得分:8)
在您的示例中,生成器在脚本结束之前不会收集垃圾。 Python不知道你是否会再次使用cd
,所以它不能扔掉它。准确地说,全局命名空间中的生成器仍然存在引用。
当引用计数降至零时,生成器将获得GCed,就像任何其他对象一样。即使发电机没用完。
这可能发生在许多正常情况下 - 如果它的本地名称超出范围,如果它是del
,如果它的所有者获得GCed。但是,如果任何活动对象(包括名称空间)拥有对它的强引用,它将不会获得GCed。
答案 2 :(得分:4)
Python垃圾收集器并不那么聪明。即使您在该行之后不再引用cd
,引用仍然存在于局部变量中,因此无法收集。 (事实上,你正在使用的一些代码可能会在你的局部变量中挖掘它并复活它。不太可能,但可能。所以Python不能做出任何假设。)
如果您想让垃圾收集器在这里做一些事情,请尝试添加:
del cd
这将删除局部变量,允许收集对象。
答案 3 :(得分:0)
其他答案解释说gc.collect()
不会垃圾收集任何仍然引用它的东西。生成器仍有实时参考cd
,因此在删除cd
之前不会进行gc编辑。
然而,此外,OP正在使用此行创建一个对该对象的SECOND强引用,该行调用弱引用对象:
cdw = weakref.ref(cd)()
因此,如果要做del cd
并致电gc.collect()
,则生成器仍不会被gc'因为cdw
也是参考。
要获取实际的弱引用,请不要调用weakref.ref
对象。只需这样做:
cdw = weakref.ref(cd)
现在删除cd
并收集垃圾时,引用计数将为零,并且调用弱引用将导致None
,如预期的那样。