22

当不再使用生成器时,它应该被垃圾收集,对吧?我尝试了以下代码,但我不确定我错了哪一部分。

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(),它可以成功打印所有剩余的,直到 StopIteration。

我尝试了这个,因为我想了解生成器和协程是如何工作的。在 David Beazley 的 PyCon 演示文稿“A Curious Course on Coroutines and Concurrency”的幻灯片 28 中,他说协程可能会无限期地运行,我们应该使用.close()它来关闭它。然后他说垃圾收集器会调用.close()。在我的理解中,一旦我们调用.close()了自己,gc就会.close()再次调用。会gc收到无法调用.close()已经关闭的协程的警告吗?

感谢您的任何意见。

4

4 回答 4

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

对象(包括生成器)在它们的引用计数达到 0 时被垃圾收集(在 Cpython 中——其他实现可能会以不同的方式工作)。在 Cpython 中,只有当你看到一个del语句,或者当一个对象因为当前命名空间改变而超出范围时,引用计数才会减少。

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

于 2013-03-19T01:50:56.707 回答
8

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

当一个生成器的引用计数降到零时,它就会被 GCed ,就像任何其他对象一样。即使发电机没有耗尽。

这可能在许多正常情况下发生 - 如果它位于超出范围的本地名称中,如果它被deled,如果它的所有者被 GCed。但是如果任何活动对象(包括命名空间)持有对它的强引用,它就不会被 GCed。

于 2013-03-19T01:53:14.123 回答
4

Python 垃圾收集器并不那么聪明。即使您cd在该行之后不再引用,该引用仍然存在于局部变量中,因此无法收集。(事实上​​,您使用的某些代码可能会在您的局部变量中挖掘并恢复它。不太可能,但有可能。因此 Python 无法做出任何假设。)

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

del cd

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

于 2013-03-19T01:51:20.423 回答
1

其他答案已经解释说,gc.collect()不会垃圾收集仍然引用它的任何东西。仍然有cd对生成器的实时引用,因此在删除之前不会被 gc'ed cd

但是,此外,OP 正在使用此行创建对对象的 SECOND 强引用,该行调用弱引用对象:

cdw = weakref.ref(cd)()

因此,如果要del cd调用并调用gc.collect(),生成器仍然不会被 gc'ed,因为cdw它也是一个引用。

要获取实际的弱引用,请不要调用该weakref.ref对象。只需这样做:

cdw = weakref.ref(cd)

现在,当cd被删除并被垃圾回收时,引用计数将为零,调用弱引用将导致None,正如预期的那样。

于 2017-08-07T04:55:26.387 回答