Как подключиться к сигналу GObject в python, не сохраняя ссылку на соединитель?

StackOverflow https://stackoverflow.com/questions/1364923

  •  21-09-2019
  •  | 
  •  

Вопрос

Проблема в основном заключается в этом, в привязках python к gobject и gtk.Предположим, у нас есть класс, который привязывается к сигналу при построении:

class ClipboardMonitor (object):
  def __init__(self):
    clip = gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD)
    clip.connect("owner-change", self._clipboard_changed)

Проблема сейчас в том, что, ни один экземпляр ClipboardMonitor никогда не умрет.Буфер обмена gtk - это объект всего приложения, и при подключении к нему сохраняется ссылка на объект, поскольку мы используем обратный вызов self._clipboard_changed.

Я обсуждаю, как обойти это, используя слабые ссылки (модуль weakref), но мне еще предстоит придумать план.У кого-нибудь есть идея, как передать обратный вызов для регистрации сигнала и заставить его вести себя как слабая ссылка (если обратный вызов сигнала вызывается, когда экземпляр ClipboardMonitor находится вне области видимости, это не должно быть операцией).

Дополнение: Сформулировано независимо от GObject или GTK+:

Как вы предоставляете метод обратного вызова непрозрачному объекту с семантикой weakref?Если подключаемый объект выходит за пределы области видимости, его следует удалить, а обратный вызов должен действовать как no-op;подключаемый объект не должен содержать ссылку на соединитель.

Чтобы прояснить:Я явно хочу избежать необходимости вызывать метод "деструктор / финализатор"

Это было полезно?

Решение

Стандартный способ - отключить сигнал.Однако для этого в вашем классе должен быть метод, подобный деструктору, вызываемый явно кодом, который поддерживает ваш объект.Это необходимо, потому что в противном случае вы получите циклическую зависимость.

class ClipboardMonitor(object):
    [...]

    def __init__(self):
        self.clip = gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD)
        self.signal_id = self.clip.connect("owner-change", self._clipboard_changed)

    def close(self):
        self.clip.disconnect(self.signal_id)

Как вы указали, вам нужны слабые ссылки, если вы хотите избежать явного уничтожения.Я бы написал слабую фабрику обратного вызова, например:

import weakref

class CallbackWrapper(object):
    def __init__(self, sender, callback):
        self.weak_obj = weakref.ref(callback.im_self)
        self.weak_fun = weakref.ref(callback.im_func)
        self.sender = sender
        self.handle = None

    def __call__(self, *things):
        obj = self.weak_obj()
        fun = self.weak_fun()
        if obj is not None and fun is not None:
            return fun(obj, *things)
        elif self.handle is not None:
            self.sender.disconnect(self.handle)
            self.handle = None
            self.sender = None

def weak_connect(sender, signal, callback):
    wrapper = CallbackWrapper(sender, callback)
    wrapper.handle = sender.connect(signal, wrapper)
    return wrapper

(это код подтверждения концепции, работает для меня - вам, вероятно, следует адаптировать эту часть к вашим потребностям).Несколько заметок:

  • Я сохраняю объект обратного вызова и функцию отдельно.Вы не можете просто создать weakref связанного метода, потому что связанные методы являются очень временными объектами.На самом деле weakref.ref(obj.method) уничтожит связанный объект метода мгновенно после создания weakref .Я не проверял, нужно ли также сохранять weakref для функции...Я думаю, если ваш код статичен, вы, вероятно, сможете избежать этого.
  • Оболочка объекта удалит себя из отправителя сигнала, когда заметит, что слабая ссылка перестала существовать.Это также необходимо для уничтожения циклической зависимости между CallbackWrapper и объектом отправителя сигнала.

Другие советы

(Этот ответ отслеживает мой прогресс)

Эта вторая версия также отключится;У меня есть удобная функция для gobjects, но на самом деле мне нужен этот класс для более общего случая - как для обратных вызовов сигнала D-Bus, так и для обратных вызовов GObject.

В любом случае, что можно назвать WeakCallback стиль реализации?Это очень чистая инкапсуляция слабого обратного вызова, но со незаметно добавленной специализацией gobject / dbus.Лучше написать два подкласса для этих двух случаев.

import weakref

class WeakCallback (object):
    """A Weak Callback object that will keep a reference to
    the connecting object with weakref semantics.

    This allows to connect to gobject signals without it keeping
    the connecting object alive forever.

    Will use @gobject_token or @dbus_token if set as follows:
        sender.disconnect(gobject_token)
        dbus_token.remove()
    """
    def __init__(self, obj, attr):
        """Create a new Weak Callback calling the method @obj.@attr"""
        self.wref = weakref.ref(obj)
        self.callback_attr = attr
        self.gobject_token = None
        self.dbus_token = None

    def __call__(self, *args, **kwargs):
        obj = self.wref()
        if obj:
            attr = getattr(obj, self.callback_attr)
            attr(*args, **kwargs)
        elif self.gobject_token:
            sender = args[0]
            sender.disconnect(self.gobject_token)
            self.gobject_token = None
        elif self.dbus_token:
            self.dbus_token.remove()
            self.dbus_token = None

def gobject_connect_weakly(sender, signal, connector, attr, *user_args):
    """Connect weakly to GObject @sender's @signal,
    with a callback in @connector named @attr.
    """
    wc = WeakCallback(connector, attr)
    wc.gobject_token = sender.connect(signal, wc, *user_args)

на самом деле еще не пробовал, но:

class WeakCallback(object):
    """
    Used to wrap bound methods without keeping a ref to the underlying object.
    You can also pass in user_data and user_kwargs in the same way as with
    rpartial. Note that refs will be kept to everything you pass in other than
    the callback, which will have a weakref kept to it.
    """
    def __init__(self, callback, *user_data, **user_kwargs):
        self.im_self = weakref.proxy(callback.im_self, self._invalidated)
        self.im_func = weakref.proxy(callback.im_func)
        self.user_data = user_data
        self.user_kwargs = user_kwargs

    def __call__(self, *args, **kwargs):
        kwargs.update(self.user_kwargs)
        args += self.user_data
        self.im_func(self.im_self, *args, **kwargs)

    def _invalidated(self, im_self):
        """Called by the weakref.proxy object."""
        cb = getattr(self, 'cancel_callback', None)
        if cb is not None:
            cb()

    def add_cancel_function(self, cancel_callback):
        """
        A ref will be kept to cancel_callback. It will be called back without
        any args when the underlying object dies.
        You can wrap it in WeakCallback if you want, but that's a bit too
        self-referrential for me to do by default. Also, that would stop you
        being able to use a lambda as the cancel_callback.
        """
        self.cancel_callback = cancel_callback

def weak_connect(sender, signal, callback):
    """
    API-compatible with the function described in
    http://stackoverflow.com/questions/1364923/. Mostly used as an example.
    """
    cb = WeakCallback(callback)
    handle = sender.connect(signal, cb)
    cb.add_cancel_function(WeakCallback(sender.disconnect, handle))
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top