我试图在我的应用程序中查找错误的根本原因,并最终获得了我不理解的MCVE(稍后包括)。
我的思路是这样的:
import gc, gi, weakref
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
widget = Gtk.Window()
widget.connect('destroy', Gtk.main_quit)
widget.connect('draw', lambda *args: print('draw'))
widget.show()
widget_wr = weakref.ref(widget)
del widget
gc.collect()
print('widget_wr() -> {!r}'.format(widget_wr())) # prints: `widget_wr() -> None`
Gtk.main()
在上面的代码中,即使widget
被垃圾回收了(我猜因为widget_wr()
为None),窗口小部件仍然可见,调整了大小(导致它发出{{ 1}}发出信号)控制台中的垃圾邮件draw
,然后关闭窗口退出主循环。
因此,在我的MCVE中,我基本上是在尝试检查draw
是否会以某种方式创建对widget.connect(..., my_callback)
的强引用,从而至少在以下情况下阻止my_callback
被垃圾回收:窗口小部件在用户的屏幕上可见,并且可以进行交互,尽管my_callback
已经(同样在我的猜测中)已经被垃圾收集了。
这是我最终得到的MCVE:
widget
按原样运行上面的命令时,输出为:
import gc, gi, sys, weakref
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class MyObject:
def __init__(self, widget, store_widget):
self.x = 123
if store_widget:
self.widget = widget
def method(self, *args):
print("getattr(self, 'x', default='nothing') -> {!r}"
.format(getattr(self, 'x', 'nothing')))
widget = Gtk.Window()
obj = MyObject(widget, store_widget=True) # or try store_widget=False
weakref.finalize(widget, print, 'widget finalize')
weakref.finalize(obj, print, 'obj finalize')
widget.connect('draw', obj.method)
widget.connect('destroy', Gtk.main_quit)
widget.show()
obj_wr = weakref.ref(obj)
widget_wr = weakref.ref(widget)
del obj
del widget
print('Garbage collecting...')
gc.collect()
print('Garbage collected.')
print('obj_wr() -> {}, widget_wr() -> {}'.format(obj_wr(), widget_wr()))
if obj_wr() is not None:
print('sys.getrefcount(obj_wr()) -> {}, gc.get_referrers(obj_wr()) -> {}'
.format(sys.getrefcount(obj_wr()), gc.get_referrers(obj_wr())))
Gtk.main()
因此,我得出结论,也许Garbage collecting...
widget finalize
obj finalize
Garbage collected.
obj_wr() -> None, widget_wr() -> None
getattr(self, 'x', default='nothing') -> 'nothing'
getattr(self, 'x', default='nothing') -> 'nothing'
... <and so on as the window is resized>
不会以某种方式间接创建对widget.connect(..., my_callback)
的强引用,但我仍然感到奇怪的是,无论何时窗口被调用,my_callback
仍在被调用调整了大小,尽管obj.method()
已被垃圾回收。
然后,(在我创建MCVE的过程中),我修改了代码,使得obj
不会保留为widget
的属性,此更改通过以下对高于MCVE:
obj
然后我得到的输出是:
obj = MyObject(widget, store_widget=False)
这次,以某种方式阻止了widget finalize
Garbage collecting...
Garbage collected.
obj_wr() -> <__main__.MyObject object at 0x10f478208>, widget_wr() -> None
sys.getrefcount(obj_wr()) -> 2, gc.get_referrers(obj_wr()) -> [<bound method MyObject.method of <__main__.MyObject object at 0x10f478208>>]
getattr(self, 'x', default='nothing') -> 123
getattr(self, 'x', default='nothing') -> 123
... <and so on>
被垃圾回收。
有人知道发生了什么吗?而且,另一个问题是我是否应该维护每个小部件的引用以防止它们被gc引用,还是像第一个示例那样“创建并忘记”就可以了。
感谢阅读。
编辑:
尝试了另一种变化,将obj
类的初始化程序修改为此:
MyObject
并将其添加到class MyObject:
def __init__(self, widget, store_widget):
self.x = 123
self.widget = widget
widget.__my_obj_ref = self # store a reference to self in widget
...
的末尾:
Gtk.main()
输出:
print('sys.getrefcount(widget_wr()) -> {}, gc.get_referrers(widget_wr()) -> {}'
.format(sys.getrefcount(widget_wr()), gc.get_referrers(widget_wr())))
这一次阻止Garbage collecting...
Garbage collected.
obj_wr() -> <__main__.MyObject object at 0x10ab55208>, widget_wr() -> <Gtk.Window object at 0x10ba600d8 (GtkWindow at 0x7fb81b00a280)>
sys.getrefcount(obj_wr()) -> 3, gc.get_referrers(obj_wr()) -> [{'_MyObject__my_obj_ref': <__main__.MyObject object at 0x10ab55208>}, <bound method MyObject.method of <__main__.MyObject object at 0x10ab55208>>]
sys.getrefcount(widget_wr()) -> 3, gc.get_referrers(widget_wr()) -> [{'widget': <Gtk.Window object at 0x10ba600d8 (GtkWindow at 0x7fb81b00a280)>, 'x': 123}]
getattr(self, 'x', default='nothing') -> 123
getattr(self, 'x', default='nothing') -> 123
...
和obj
都被垃圾回收。