如何使用 View 避免内存泄漏
How to avoid memory leaks with View
使用以下程序:
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)
returns
deleting <__main__.A object at 0x12a016a70>
这是应该的。
如果我这样做
a = A()
a.configure_traits()
关闭对话框后:
del(a)
我有同样的留言:
deleting <__main__.A object at 0x12a016650>
没有提到 NewView 被删除。
总的来说,避免 Traits 和 TraitsUI 内存泄漏的良好做法是什么?
这里发生的事情是 NewView
对象涉及引用循环,并且该循环中的对象不会作为 CPython 的主要引用的一部分自动收集- 基于计数的对象释放机制。然而,它们最终应该作为 CPython 的循环垃圾收集器的一部分被收集,或者您可以通过执行 gc.collect()
来强制收集,因此这里应该没有实际的长期内存泄漏。
具有讽刺意味的是,尝试通过向 NewView
添加 __del__
方法来检测最终收集会阻碍该过程,因为它会使 NewView
对象无法收集:至少在 Python 2、Python不会尝试收集包含具有__del__
方法的对象的循环。见gc
docs for details. (Python 3 is somewhat cleverer here, thanks to the changes outlined in PEP 442.) 所以用__del__
方法,使用Python2,时间一长确实会出现缓慢的内存泄漏。解决方案是删除 __del__
方法。
这是一个显示引用循环的图(实际上,这显示了包含 NewView
对象的对象图的整个强连接组件):节点是涉及的对象,箭头从引用者指向引用者.在图表的右下部分,您看到 NewView
对象引用了其顶级 Group
(通过 content
属性),并且 Group
对象具有对原始视图的引用(container
属性)。视图的其他地方也有类似的循环。
可能值得在 Traits UI 跟踪器上打开一个功能请求:理论上,当不再需要视图时应该可以手动中断引用循环,但在实践中可能需要大量特征 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
使用以下程序:
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)
returns
deleting <__main__.A object at 0x12a016a70>
这是应该的。
如果我这样做
a = A()
a.configure_traits()
关闭对话框后:
del(a)
我有同样的留言:
deleting <__main__.A object at 0x12a016650>
没有提到 NewView 被删除。
总的来说,避免 Traits 和 TraitsUI 内存泄漏的良好做法是什么?
这里发生的事情是 NewView
对象涉及引用循环,并且该循环中的对象不会作为 CPython 的主要引用的一部分自动收集- 基于计数的对象释放机制。然而,它们最终应该作为 CPython 的循环垃圾收集器的一部分被收集,或者您可以通过执行 gc.collect()
来强制收集,因此这里应该没有实际的长期内存泄漏。
具有讽刺意味的是,尝试通过向 NewView
添加 __del__
方法来检测最终收集会阻碍该过程,因为它会使 NewView
对象无法收集:至少在 Python 2、Python不会尝试收集包含具有__del__
方法的对象的循环。见gc
docs for details. (Python 3 is somewhat cleverer here, thanks to the changes outlined in PEP 442.) 所以用__del__
方法,使用Python2,时间一长确实会出现缓慢的内存泄漏。解决方案是删除 __del__
方法。
这是一个显示引用循环的图(实际上,这显示了包含 NewView
对象的对象图的整个强连接组件):节点是涉及的对象,箭头从引用者指向引用者.在图表的右下部分,您看到 NewView
对象引用了其顶级 Group
(通过 content
属性),并且 Group
对象具有对原始视图的引用(container
属性)。视图的其他地方也有类似的循环。
可能值得在 Traits UI 跟踪器上打开一个功能请求:理论上,当不再需要视图时应该可以手动中断引用循环,但在实践中可能需要大量特征 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