2013-03-19 17 views
12

Wenn ein Generator nicht mehr verwendet wird, sollte es Müll gesammelt werden, oder? Ich habe den folgenden Code ausprobiert, aber ich bin mir nicht sicher, an welcher Stelle ich falsch lag.Wird ein Python-Generator Müll gesammelt, wenn er nicht mehr verwendet wird, aber noch nicht StopIteration erreicht?

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

Auf der vorletzten Zeile, rief ich Garbage Collector und da es keinen Aufruf cd mehr. gc sollte frei cd richtig sein. Aber wenn ich cdw.next() anrufe, druckt es noch 8. Ich versuchte ein paar mehr cdw.next(), es konnte den ganzen Rest bis StopIteration erfolgreich drucken.

Ich versuchte dies, weil ich verstehen wollte, wie Generator und Coroutine arbeiten. Auf Folie 28 von David Beazleys PyCon-Präsentation "Ein kurioser Kurs über Coroutines und Parallelität" sagte er, dass eine Coroutine auf unbestimmte Zeit laufen könnte, und wir sollten .close() verwenden, um sie herunterzufahren. Dann sagte er, dass Garbage Collector .close() anrufen wird. In meinem Verständnis, sobald wir .close() selbst aufgerufen haben, wird gc wieder .close() aufrufen. Wird gc eine Warnung erhalten, dass es .close() auf einer bereits geschlossenen Coroutine nicht aufrufen kann?

Danke für alle Eingaben.

Antwort

7

Aufgrund der dynamischen Natur von Python wird der Verweis auf cd nicht freigegeben, bis Sie das Ende der aktuellen Routine erreichen, weil (zumindest) die Cpython-Implementierung von Python nicht "vorauslesen". (Wenn Sie nicht wissen, welche Python-Implementierung Sie verwenden, ist es fast sicher "Cpython"). Es gibt eine Reihe von Feinheiten, die es dem Interpreter praktisch unmöglich machen zu bestimmen, ob ein Objekt frei sein soll, wenn es im allgemeinen Fall immer noch im aktuellen Namespace vorhanden ist (z. B. können Sie es immer noch durch einen Anruf an locals() erreichen).

In einigen weniger allgemeinen Fällen können andere Python-Implementierungen möglicherweise ein Objekt vor dem Ende des aktuellen Stapelrahmens freigeben, aber Cpython stört nicht.

diesen Code stattdessen versuchen, die zeigt, dass der Generator bereinigt werden in CPython kostenlos:

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

Objekte (einschließlich Generatoren) werden gesammelt Müll, wenn ihre Referenzzähler 0 erreicht (in CPython - Andere Implementierungen kann anders arbeiten). In Cpython werden Referenzzähler nur verringert, wenn eine del -Anweisung angezeigt wird oder wenn ein Objekt den Gültigkeitsbereich verlässt, da sich der aktuelle Namespace ändert.

Wichtig ist, dass sobald es keine Referenzen mehr auf ein Objekt gibt, es frei ist, vom Garbage Collector aufgeräumt zu werden. Die Details, wie die Implementierung feststellt, dass keine Referenzen mehr vorhanden sind, bleiben den Implementierern der von Ihnen verwendeten Python-Distribution vorbehalten.

+0

Eigentlich könnte PyPy das tun, wenn 'func' in einer Schleife aufgerufen wird. Es kann nicht einmal den Generator überhaupt zuordnen. (Ich bezweifle es derzeit, wenn Generatoren derzeit so unoptimiert sind, wie Fijal sagt, aber dies tut dies bereits mit vielen anderen Objekten.) Das Problem ist nicht, dass Python dynamisch ist, das Problem ist, dass CPython nicht einmal versucht, zu regieren aus Dynamik, auch wenn es möglich wäre. Natürlich ist die Refcount-Erklärung am Ende auch CPython-spezifisch. Das 'c()' in Ihrem Beispiel kann sehr gut etwas in PyPy, Jython oder IronPython drucken. – delnan

+1

@delnan - Sie haben das Referenzzählungsbit korrekt. Wie behandelt PyPy den Fall, in dem Sie etwas wie 'localhosts() [other_str [:: 2] [:: - 1]]' machen, wenn es nicht vor der Zeit weiß, was 'other_str' (und folglich ist es Scheiben) werden sein? Zugegeben, das ist ein wahnsinniger Fall, aber der Python-Interpreter sollte es erlauben. Ich denke, es könnte nach "Einheimische", "Vars" und "Globals" aussehen und die sofortige Garbage-Collection nur dann deaktivieren, wenn sie nicht vorhanden sind oder etwas ... aber ich denke, dass die dynamische Natur die sofortige Sammlung verhindert *manche Fälle ... – mgilson

+2

Es handhabt so, wie es alle anderen dynamischen Seltsamkeiten behandelt (seltsame Typen tauchen auf, ersetzt Methoden oder Klassen oder Module, fummelt mit '__dict__', etc): Es kommt aus dem optimierten Code heraus und macht das ineffiziente Ding (manchmal sogar noch mehr als das, was CPython macht, beginnend mit dem, was es bereits getan hätte (z. B. das Zuweisen von Zwischenobjekten), wenn es überhaupt nicht optimiert worden wäre. Dies verfolgt den JIT-Compiler 101. Wie hat es PyPy gelungen, * irgendetwas * zu optimieren? – delnan

4

Der Python-Garbage Collector ist nicht ganz so schlau. Auch wenn Sie nach dieser Zeile nicht mehr auf cd verweisen, ist die Referenz in lokalen Variablen immer noch aktiv, so dass sie nicht erfasst werden kann. (In der Tat ist es möglich, dass Code, den Sie verwenden, in Ihren lokalen Variablen herumwirbeln und ihn wieder auferstehen lässt. Unwahrscheinlich, aber möglich. Daher kann Python keine Annahmen treffen.

)

Wenn der Garbage Collector machen wollen hier tatsächlich etwas tun, versuchen Sie:

del cd 

Dies wird die lokale Variable entfernen, so dass das Objekt gesammelt werden.

8

In Ihrem Beispiel wird der Generator keine Müllsammlung bis zum Ende des Skripts erhalten. Python weiß nicht, ob Sie wieder cd verwenden werden, damit es nicht weggeworfen werden kann. Um es genau zu sagen, es gibt immer noch eine Referenz zu Ihrem Generator in der globalen Namespace.

Ein Generator erhält GCed, wenn sein Referenzzähler wie jedes andere Objekt auf Null fällt. Auch wenn der Generator nicht erschöpft ist.

Dies kann unter vielen normalen Umständen passieren - wenn es in einem lokalen Namen ist, der außerhalb des Geltungsbereiches fällt, wenn es del ed ist, wenn sein Besitzer GCed erhält. Wenn jedoch Live-Objekte (einschließlich Namespaces) starke Referenzen enthalten, wird GCed nicht erkannt.

+0

Dies ist auch eine gute Antwort. Vielen Dank. – foresightyj

0

Die anderen Antworten haben erklärt, dass gc.collect() nicht Müll sammelt alles, was noch Referenzen darauf hat. Es gibt immer noch eine Live-Referenz cd an den Generator, so dass es nicht gc'ed wird, bis cd gelöscht wird.

jedoch zusätzlich wird das OP eine zweite starke Referenz auf das Objekt der Erstellung dieser Linie verwendet wird, die die schwache Referenz-Objekt aufruft:

cdw = weakref.ref(cd)() 

Also, wenn man del cd und gc.collect() täte nennen, wobei der Generator würde immer noch nicht gc'ed sein, weil cdw auch eine Referenz ist.

Um eine tatsächliche schwache Referenz zu erhalten, rufen Sie nicht das Objekt weakref.ref auf. Einfach dies zu tun:

cdw = weakref.ref(cd) 

Nun, wenn cd gelöscht und Müll gesammelt, wird der Referenzzähler Null und rief die schwache Referenz in None führen wird, wie erwartet.

Verwandte Themen