2016-11-28 3 views
5

Mit dem folgenden Programm vermeiden:Wie Speicherlecks mit Blick

from traits.api import HasTraits, Int, Instance 
from traitsui.api import View 

class NewView(View): 
    def __del__(self): 
     print('deleting NewView') 

class A(HasTraits): 
    new_view = Instance(NewView) 
    def __del__(self): 
     print('deleting {}'.format(self)) 

    a = Int 

    def default_traits_view(self): 
     new_view = NewView('a') 
     return new_view 

laufen

a = A() 
del(a) 

kehrt

deleting <__main__.A object at 0x12a016a70> 

wie es sollte.

Wenn ich

a = A() 
a.configure_traits() 

und nach dem Schließen des Dialog:

del(a) 

ich die gleiche Art von Nachricht haben:

deleting <__main__.A object at 0x12a016650> 

ohne Erwähnung des NewView gelöscht .

Im Allgemeinen, was sind die guten Praktiken, um Speicherlecks mit Traits und TraitsUI zu vermeiden?

+1

Gute Frage, Yves, mit dem Leck scheinbar durch ein paar schnelle Experimentieren mit einem 'weakref' zu' new_view' bestätigt . Meine erste (sehr vorläufige) Vermutung ist, dass Views leicht sind und sich nicht proliferieren lassen, was nicht als ein signifikantes Problem angesehen wurde. Meine zweite Vermutung ist, dass es versteckte Magie gibt. :) Ich habe einige TraitsUI-Assistenten um eine Erklärung gebeten. Wenn wir keine Antwort erhalten, würde ich vorschlagen, sie als TraitsUI-Problem zu veröffentlichen, wo sie im Laufe der Zeit sichtbar bleibt. –

Antwort

4

Hier ist das NewView Objekt in einem Referenzzyklus beteiligt, und die Objekte in diesem Zyklus werden nicht automatisch als Teil von CPythons primärem Referenzzählungs-basierten Objekt-Deallokationsmechanismus erfasst. Sie sollten jedoch als Teil von CPythons zyklischem Garbage Collector gesammelt werden, oder Sie können diese Sammlung erzwingen, indem Sie eine gc.collect() ausführen, so dass es hier keinen tatsächlichen Langzeitspeicherverlust geben sollte.

Ironischer, die eventuelle Übergabe zu erfassen versuchen durch Zugabe einer __del__ Methode NewView behindert den Prozess, da es das Objekt NewView uncollectable rendert: zumindest in Python 2, Python wird nicht versuchen Zyklen zu sammeln Objekte enthalten, die __del__ haben Methoden. Details finden Sie unter gc docs. (Python 3 ist hier etwas cleverer, dank der Änderungen, die in PEP 442 beschrieben sind.) Mit der Methode __del__, die Python 2 verwendet, wird es im Laufe der Zeit tatsächlich einen langsamen Speicherverlust geben. Die Lösung besteht darin, die __del__ Methode zu entfernen.

Hier ist ein Diagramm, das den Referenzzyklus zeigt (tatsächlich zeigt dies die gesamte stark verbundene Komponente des Objektgraphen mit dem NewView Objekt): Knoten sind die beteiligten Objekte, und Pfeile gehen von Referrern zu Referenten. Im rechten unteren Teil des Diagramms sehen Sie, dass das Objekt NewView einen Verweis auf seine oberste Ebene Group (über das Attribut content) hat und dass das Objekt Group einen Verweis zurück auf die ursprüngliche Ansicht hat (das Attribut container). Es gibt ähnliche Zyklen, die anderswo in der Ansicht stattfinden.

NewView reference cycle

Es ist wahrscheinlich lohnt sich ein Feature-Request auf dem Traits UI-Tracker öffnen: in der Theorie sollte es möglich sein, die Referenzzyklen manuell zu brechen, wenn der Blick nicht mehr benötigt wird, obwohl in der Praxis, die erhebliche erfordern könnten Überarbeitung der Traits UI-Quelle.

Hier einige Code, dass (mit den __del__ Methoden entfernt) zeigt ein Aufruf an gc.collect das NewView Objekt sammelt: es speichert einen schwachen Verweis auf die Ansicht auf das A Beispiel mit einem Rückruf, der meldet, wenn diese Ansicht Müll gesammelt.

from traits.api import HasTraits, Int, Instance 
from traitsui.api import View 

import gc 
import weakref 

class NewView(View): 
    pass 


def report_collection(ref): 
    print("NewView object has been collected") 


class A(HasTraits): 
    a = Int 

    def default_traits_view(self): 
     new_view = NewView('a') 
     self.view_ref = weakref.ref(new_view, report_collection) 
     return new_view 


def open_view(): 
    a = A() 
    a.configure_traits() 
    print("Collecting cyclic garbage") 
    gc.collect() 
    print("Cyclic garbage collection complete") 

Auf meinem Rechner ist hier, was ich sehe, wenn open_view genannt wird:

>>> open_view() 
Collecting cyclic garbage 
NewView object has been collected 
Cyclic garbage collection complete 
+0

Danke für die sehr detaillierte Antwort. Ich musste einfach 'kind = 'modal'' zur' new_view'definition auf meinem Mac hinzufügen, damit der Code das Ergebnis liefert. Ich benutzte diese transienten Ansichten mit Chaco-Plots und riesigen (15Mpix) Bildern, und ich explodierte die Erinnerung –