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