如何使用View避免内存泄漏

时间:2016-11-28 08:35:57

标签: enthought traitsui

使用以下程序:

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

运行

a = A()
del(a)

返回

 deleting <__main__.A object at 0x12a016a70>

应该如此。

如果我这样做

a = A()
a.configure_traits()

关闭对话框后:

del(a)

我有同样的信息:

deleting <__main__.A object at 0x12a016650>

没有提到要删除的NewView。

在Geneal中,使用Traits和TraitsUI避免内存泄漏的良好做法是什么?

1 个答案:

答案 0 :(得分:4)

这里发生的是NewView对象涉及参考周期,并且该周期中的对象不会作为CPython的主要内容自动收集基于引用计数的对象解除分配机制。但是,它们最终应该作为CPython循环垃圾收集器的一部分收集,或者你可以通过执行gc.collect()来强制收集,所以这里不应该有实际的长期内存泄漏。

具有讽刺意味的是,尝试通过向__del__添加NewView方法来检测最终集合会阻碍该过程,因为它会使NewView对象无法收集:至少在Python 2中,Python赢了&尝试收集包含__del__方法的对象的循环。有关详细信息,请参阅gc docs。 (由于PEP 442中概述的更改,Python 3在这里有点聪明。)因此,使用Python {2}的__del__方法,随着时间的推移确实会出现缓慢的内存泄漏。解决方案是删除__del__方法。

这里有一个显示参考周期的图表(实际上,它显示了包含NewView对象的对象图的整个强连接组件):节点是所涉及的对象,箭头从引用者到所指。在图表的右下角,您会看到NewView对象引用了其顶级Group(通过content属性),Group object有一个返回原始视图的引用(container属性)。在视图的其他地方也有类似的周期。

NewView reference cycle

可能值得在Traits UI跟踪器上打开功能请求:理论上,应该可以在不再需要视图时手动中断参考周期,但在实践中可能需要对Traits UI源。

这里有一些代码可以证明(删除了__del__方法)对gc.collect的调用会收集NewView对象:它会存储对视图的弱引用在A实例上,使用回调报告该视图何时被垃圾收集。

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

在我的机器上,这是我在调用open_view时看到的内容:

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