generadores - ¿Se recolectará la basura de un generador de Python si ya no se va a usar pero aún no ha llegado a StopIteration?
iteradores y generadores en python (4)
Cuando un generador ya no se usa, debe ser recolectado basura, ¿verdad? Probé el siguiente código, pero no estoy seguro de qué parte estaba equivocada.
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()
En la segunda última línea, llamé al recolector de basura y, como ya no hay más llamadas para cd
. gc
debe liberar cd
derecho. Pero cuando llamo cdw.next()
, todavía se está imprimiendo 8. Intenté algunos cdw.next()
, podría imprimir todo el resto hasta que se detuviera la función StopIteration.
Intenté esto porque quería entender cómo funcionan el generador y la coroutina. En la diapositiva 28 de la presentación de PyCon de David Beazley "Un curso curioso sobre coroutines y concurrencias", dijo que una coroutine podría correr indefinidamente y deberíamos usar .close()
para cerrarla. Luego dijo que el recolector de basura llamará .close()
. En mi entendimiento, una vez que llamamos a .close()
nosotros mismos, gc
llamará a .close()
nuevo. ¿ gc
recibirá una advertencia de que no puede llamar a .close()
en una coroutina ya cerrada?
Gracias por cualquier entrada.
Debido a la naturaleza dinámica de python, la referencia a cd
no se libera hasta que llega al final de la rutina actual porque (al menos) la implementación de python en Cpython no se "lee a continuación". (Si no sabe qué implementación de python está utilizando, es casi seguro que es "Cpython"). Hay una serie de sutilezas que harían que sea prácticamente imposible para el intérprete determinar si un objeto debería estar libre si aún existe en el espacio de nombres actual en el caso general (por ejemplo, aún puede contactarlo llamando a locals()
) .
En algunos casos menos generales, otras implementaciones de python pueden liberar un objeto antes del final del marco de pila actual, pero Cpython no se molesta.
Pruebe este código en su lugar, lo que demuestra que el generador se puede limpiar libremente en 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()
Los objetos (incluidos los generadores) se recogen cuando el recuento de referencias llega a 0 (en Cpython: otras implementaciones pueden funcionar de manera diferente). En Cpython, los recuentos de referencias solo se reducen cuando ve una declaración del
, o cuando un objeto sale del ámbito debido a que el espacio de nombres actual cambia.
Lo importante es que una vez que no haya más referencias a un objeto, el recolector de basura puede limpiarlo de forma gratuita. Los detalles de cómo la implementación determina que no hay más referencias se dejan a los implementadores de la distribución de python en particular que estás usando.
El recolector de basura de Python no es tan inteligente. Aunque ya no hace referencia a cd
después de esa línea, la referencia sigue en vivo en las variables locales, por lo que no se puede recopilar. (De hecho, es posible que algunos códigos que estés usando puedan indagar en tus variables locales y resucitarlo. Es poco probable, pero es posible. Por lo tanto, Python no puede hacer suposiciones).
Si quiere que el recolector de basura haga algo aquí, intente agregar:
del cd
Esto eliminará la variable local, permitiendo que el objeto sea recolectado.
En su ejemplo, el generador no recogerá la basura hasta el final del script. Python no sabe si vas a usar cd
nuevo, así que no puede tirarlo. Para decirlo con precisión, todavía hay una referencia a su generador en el espacio de nombres global .
Un generador se pondrá en GC cuando su recuento de referencia se reduzca a cero, como cualquier otro objeto. Incluso si el generador no está agotado.
Esto puede suceder en muchas circunstancias normales: si está en un nombre local que queda fuera del alcance, si se edita, si su propietario recibe GCed. Pero si algún objeto vivo (incluidos los espacios de nombres) tiene referencias sólidas a él, no se convertirá en GCed.
Las otras respuestas han explicado que gc.collect()
no recolectará nada que todavía tenga referencias a él. Todavía hay un cd
referencia en vivo al generador, por lo que no se enviará hasta que se elimine el cd
.
Sin embargo, además, el OP está creando una SEGUNDA referencia segura para el objeto que usa esta línea, que llama al objeto de referencia débil:
cdw = weakref.ref(cd)()
Por lo tanto, si uno hiciera gc.collect()
y llamara a gc.collect()
, el generador aún no estaría conectado porque cdw
también es una referencia.
Para obtener una referencia débil real, no llame al objeto weakref.ref
. Simplemente haga esto:
cdw = weakref.ref(cd)
Ahora, cuando se elimina el cd
y se recolecta la basura, el recuento de referencia será cero y la llamada a la referencia débil dará como resultado None
, como se esperaba.