¿Cómo conectarse a una señal GObject en python, sin que mantenga una referencia al conector?

El problema es básicamente este, en los enlaces de objeto y gtk de python. Supongamos que tenemos una clase que se une a una señal cuando se construye:

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

El problema ahora es que ninguna instancia de ClipboardMonitor morirá nunca . El portapapeles gtk es un objeto de toda la aplicación, y conectarse a él mantiene una referencia al objeto, ya que usamos el callback self._clipboard_changed .

Estoy debatiendo cómo solucionar este problema utilizando referencias débiles (módulo de referencia débil), pero todavía no tengo un plan. Cualquiera tiene una idea de cómo pasar una callback al registro de la señal, y hacer que se comporte como una referencia débil (si se llama a la callback de la señal cuando la instancia de ClipboardMonitor está fuera de scope, debería ser una operación sin opción).

Adición: Fraseado independientemente de GObject o GTK +:

¿Cómo se proporciona un método de callback a un objeto opaco, con semántica débil? Si el objeto de conexión queda fuera del scope, debe eliminarse y la callback debe actuar como un no-op; el conector no debe contener una referencia al conector.

Para aclarar: quiero evitar explícitamente tener que llamar a un método “destructor / finalizador”

La forma estándar es desconectar la señal. Sin embargo, esto necesita tener un método similar a un destructor en su clase, llamado explícitamente por el código que mantiene su objeto. Esto es necesario, porque de lo contrario obtendrá una dependencia circular.

 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) 

Como señalaste, necesitas puntos débiles si quieres evitar la destrucción explícita. Me gustaría escribir una fábrica de callback débil, como:

 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 

(Este es un código de prueba de concepto, funciona para mí; probablemente debería adaptar esta pieza a sus necesidades). Algunas notas:

  • Estoy almacenando el objeto de callback y la función por separado. No puede simplemente hacer una referencia débil de un método enlazado, porque los métodos enlazados son objetos muy temporales. De hecho, weakref.ref(obj.method) destruirá el objeto del método enlazado instantáneamente después de crear una referencia débil. No verifiqué si es necesario almacenar también una referencia débil a la función … Supongo que si su código es estático, probablemente pueda evitarlo.
  • La envoltura del objeto se eliminará del remitente de la señal cuando note que la referencia débil dejó de existir. Esto también es necesario para destruir la dependencia circular entre el CallbackWrapper y el objeto emisor de señal.

(Esta respuesta sigue mi progreso)

Esta segunda versión también se desconectará; Tengo una función de conveniencia para gobjects, pero en realidad necesito esta clase para un caso más general, tanto para las devoluciones de llamada de señal D-Bus como para las devoluciones de llamada de GObject.

De todos modos, ¿cómo se puede llamar al estilo de implementación de WeakCallback ? Es una encapsulación muy limpia de la callback débil, pero con la especialización de gobject / dbus sin problemas. Beats escribiendo dos subclases para esos dos casos.

 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) 

En realidad no lo intenté todavía, pero:

 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))