2009-09-01 8 views
4

Le problème est fondamentalement ceci, dans les liaisons gobject et gtk de python. Supposons que nous avons une classe qui se lie à un signal lorsque construit:Comment se connecter à un signal GObject en python, sans garder une référence au connecteur?

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, aucun cas de ClipboardMonitor va jamais mourir. Le presse-papier gtk est un objet à l'échelle de l'application, et la connexion à celui-ci conserve une référence à l'objet, puisque nous utilisons le rappel self._clipboard_changed.

Je discute de la façon de contourner ce problème en utilisant des références faibles (module weakref), mais je n'ai pas encore trouvé de plan. Tout le monde a une idée de comment passer un rappel à l'enregistrement du signal, et se comporter comme une référence faible (si le rappel de signal est appelé quand l'instance de ClipboardMonitor est hors de portée, cela devrait être un non-op).

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

Comment fournissez-vous 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 devrait agir comme un no-op; le connecté ne doit pas contenir de référence au connecteur.

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

+1

idée supplémentaire: si vous n'avez pas besoin de débrancher, un "légèrement" idée hackish: clip.connect ("foo", (lambda * les choses, obj = weakref.ref (self) :(obj(). method (* choses) si obj() else Aucune))) - pas très sympa, mais un one-liner! – liori

Répondre

8

La méthode standard est de déconnecter le signal. Cela doit cependant avoir une méthode semblable à un destructeur dans votre classe, appelée explicitement par du code qui maintient votre objet. C'est nécessaire, sinon vous aurez une 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 souligné, vous avez besoin de weakrefs si vous voulez éviter la destruction 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 

(ceci 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 séparément. 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 de la méthode liée instantanément après avoir créé un weakref. Je n'ai pas vérifié s'il est nécessaire de stocker un weakref à la fonction aussi ... Je suppose que si votre code est statique, vous pouvez probablement éviter cela.
  • L'encapsuleur d'objet se retirera de l'expéditeur du signal lorsqu'il remarquera que la référence faible a cessé d'exister. Ceci est également nécessaire pour détruire la dépendance circulaire entre le CallbackWrapper et l'objet émetteur du signal.
+0

Bien! J'ai posté une réponse pour montrer ce que j'ai trouvé mais vous fournissez quelques pièces manquantes. Ok Questions: Les attributs im_self, im_func sont-ils documentés/fiables? J'utilise un nom d'objet/attribut. Et déconnecter le signal est un bonus, pas une exigence stricte. – u0b34a0f6ae

+1

Ils sont définis dans http://docs.python.org/reference/datamodel.html#the-standard-type-hierarchy (recherchez "Méthodes définies par l'utilisateur"). – liori

1

(Cette réponse pistes mes progrès)

Cette deuxième version déconnectera ainsi; J'ai une fonction de commodité pour les gobjects, mais j'ai vraiment besoin de cette classe pour un cas plus général - à la fois pour les rappels de signaux D-Bus et les rappels GObject.

Quoi qu'il en soit, comment peut-on appeler le style d'implémentation WeakCallback? C'est une encapsulation très propre du callback faible, mais avec la spécialisation gobject/dbus clouée de manière imperceptible. 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 @[email protected]""" 
     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) 
1

pas réellement 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)) 
Questions connexes