PyGObject: Как виджеты поддерживают ссылки на обработчики сигналов? - PullRequest
0 голосов
/ 19 января 2019

Я пытался найти основную причину ошибки в моем приложении, и мне удалось получить 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), виджет окна все еще виден, изменяя его размеры (вызывая его испускание draw сигналов) спама draw в консоли и закрытие окна выходит из основного цикла.

Так что в моем MCVE я в основном пытался проверить, создаст ли widget.connect(..., my_callback) сильную ссылку на my_callback, не позволяя my_callback собирать мусор, по крайней мере, пока виджет окна виден на экран пользователя и доступен для взаимодействия, несмотря на то, что widget уже (опять я догадываюсь) сборщик мусора.

Это MCVE, с которым я закончил:

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() все еще вызывается при каждом изменении размера окна, несмотря на obj будучи мусором.

Затем (когда я создавал MCVE) я изменил код таким образом, чтобы widget не сохранялся как атрибут obj, это изменение отражается в следующей модификации вышеуказанного MCVE:

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>

На этот раз каким-то образом obj запрещено собирать мусор.

Кто-нибудь имеет представление о том, что происходит? Кроме того, еще один вопрос заключается в том, следует ли мне поддерживать некоторые ссылки на каждый виджет, чтобы они не были gc'd, или можно просто «создать и забыть», как в самом первом примере.

Спасибо, что прочитали это.


Edit:

Попробовал другой вариант, изменил инициализатор класса 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 и widget.

...