Comment se connecter à un signal GObject en python, sans qu'il en gardant une référence au connecteur?

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

  •  21-09-2019
  •  | 
  •  

Question

Le problème est essentiellement ce, dans GObject de python et les fixations gtk. Supposons que nous avons une classe qui se lie à un signal lorsque construit:

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

Le problème est maintenant que, aucune instance de ClipboardMonitor va jamais mourir . Le presse-papiers gtk est un objet d'application à l'échelle, et la connexion à qui garde une référence à l'objet, puisque nous utilisons le self._clipboard_changed de rappel.

Je me débats à travailler autour de cela en utilisant des références faibles (module weakref), mais je dois encore venir avec un plan. Quelqu'un at-il une idée comment passer un rappel à l'enregistrement du signal, et ont se comporter comme une référence faible (si le rappel de signal est appelé lorsque l'instance de ClipboardMonitor est hors de portée, il devrait être un no-op).

Addition: Formulé indépendamment de GObject ou GTK +:

Comment fournir une méthode de rappel à un objet opaque, avec la sémantique weakref? Si l'objet de connexion est hors de portée, il devrait être supprimé et le rappel doit agir comme un non-op; le connectee ne doit pas contenir une référence au connecteur.

Pour clarifier: Je veux explicitement éviter d'avoir à appeler une méthode « destructor / finaliseur »

Était-ce utile?

La solution

La méthode standard consiste à déconnecter le signal. Cela doit cependant avoir une méthode semblable à destructor dans votre classe, appelée explicitement par le code qui maintient votre objet. Cela est nécessaire, car sinon vous aurez la dépendance circulaire.

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)

Comme vous l'avez dit, vous avez besoin weakrefs si vous voulez éviter de détruire explicite. Je voudrais écrire une usine de rappel faible, comme:

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

(ce qui est une preuve de code concept, fonctionne pour moi - vous devriez probablement adapter cette pièce à vos besoins). Quelques notes:

  • je stocke l'objet de rappel et la fonction separatelly. Vous ne pouvez pas simplement faire un weakref d'une méthode liée, car les méthodes liées sont des objets très temporaires. En fait, weakref.ref(obj.method) va détruire l'objet méthode liée instantanément après la création d'un weakref. Je n'ai pas vérifié si elle est nécessaire pour stocker une weakref à la fonction trop ... Je suppose que si votre code est statique, vous pouvez probablement éviter cela.
  • L'enveloppe d'objet va se retirer de l'émetteur du signal quand il remarque que la référence faible a cessé d'exister. Cela est également nécessaire pour détruire la dépendance circulaire entre la CallbackWrapper et l'objet émetteur de signaux.

Autres conseils

(Cette réponse pistes mes progrès)

Cette deuxième version déconnectera ainsi; J'ai une fonction pratique pour GObjects, mais je vraiment besoin de cette classe pour un cas plus général -. Tant pour les callbacks de signal D-Bus et callbacks GObject

De toute façon, que peut-on appeler le style de mise en œuvre de WeakCallback? Il est une encapsulation très propre de la fonction de rappel faible, mais avec la spécialisation gobject / dbus imperceptiblement sur clouée. Beats écrit deux sous-classes pour ces deux cas.

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)

pas vraiment encore essayé, mais:

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))
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top