PyGObject:小部件如何维护对信号处理程序的引用?

时间:2019-01-19 16:47:52

标签: python python-3.x gtk3 pygobject

我试图在我的应用程序中查找错误的根本原因,并最终获得了我不理解的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都被垃圾回收。

0 个答案:

没有答案