Будет ли сборщик Python собирать мусор, если он больше не будет использоваться, но еще не достиг StopIteration?

Когда генератор больше не используется, он должен собирать мусор, не так ли? Я попробовал следующий код, но я не уверен, в какой части я ошибся.

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.

Я пробовал это, потому что хотел понять, как работают генератор и сопрограмма. На слайде 28 презентации Дэвида Бэзли Пикона "Любопытный курс на Corouts и Concurrency" он сказал, что сопрограмма может работать бесконечно, и мы должны использовать .close(), чтобы закрыть ее. Затем он сказал, что сборщик мусора вызовет .close(). В моем понимании, как только мы назвали .close() себя, gc снова вызовет .close(). Будет ли gc получать предупреждение о том, что он не может вызвать .close() на уже закрытой сопрограмме?

Спасибо за любые входы.

Ответ 1

Из-за динамической природы питона ссылка на 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, который вы используете.

Ответ 2

В вашем примере генератор не получит мусор, собранный до конца script. Python не знает, собираетесь ли вы снова использовать cd, чтобы он не мог его выбросить. Чтобы точно сказать, в глобальном пространстве имен все еще есть ссылка на ваш генератор.

Генератор получит GCed, когда его счетчик ссылок упадет до нуля, как и любой другой объект. Даже если генератор не исчерпан.

Это может произойти при многих нормальных обстоятельствах - если это локальное имя выходит из области видимости, если оно del ed, если его владелец получает GCed. Но если какие-либо живые объекты (включая пространства имен) содержат сильные ссылки на него, он не получит GCed.

Ответ 3

Сборщик мусора Python не такой уж умный. Несмотря на то, что после этой строки вы больше не ссылаетесь на cd, ссылка по-прежнему сохраняется в локальных переменных, поэтому ее невозможно собрать. (На самом деле, возможно, что некоторый код, который вы используете, может копаться в ваших локальных переменных и воскрешать его. Вряд ли, но возможно. Поэтому Python не может делать никаких предположений.)

Если вы хотите, чтобы сборщик мусора действительно сделал что-то здесь, попробуйте добавить:

del cd

Это приведет к удалению локальной переменной, позволяющей собирать объект.

Ответ 4

Другие ответы объяснили, что gc.collect() не будет мусором собирать все, что еще имеет ссылки на него. В генераторе все еще есть ссылка cd, поэтому она не будет сохранена до тех пор, пока cd не будет удалена.

Однако, кроме того, OP создает SECOND сильную ссылку на объект, используя эту строку, которая вызывает слабый ссылочный объект:

cdw = weakref.ref(cd)()

Итак, если нужно сделать del cd и вызвать gc.collect(), генератор все равно не будет gc'ed, потому что cdw также является ссылкой.

Чтобы получить фактическую слабую ссылку, не вызывайте объект weakref.ref. Просто сделайте следующее:

cdw = weakref.ref(cd)

Теперь, когда cd удаляется и сбор мусора, счетчик ссылок будет равен нулю, а вызов слабой ссылки приведет к None, как и ожидалось.