Wie man ein GObject Signal in Python verbinden, ohne dass es einen Verweis auf das Verbindungsstück zu halten?

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

  •  21-09-2019
  •  | 
  •  

Frage

Das Problem ist im Grunde das, in Python gobject und gtk-Bindungen. Angenommen, wir eine Klasse, dass bindet an ein Signal, wenn aufgebaut:

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

Das Problem ist jetzt, dass keine Instanz von ClipboardMonitor jemals sterben. Die gtk Zwischenablage ist ein anwendungsweiten Objekt, und es verbindet hält eine Referenz auf das Objekt, da wir den Rückruf self._clipboard_changed verwenden.

Ich debattieren, wie dies mit schwachen Referenzen arbeiten um (WeakRef Modul), aber ich habe noch einen Plan zu entwickeln. Wer eine Idee, wie ein Rückruf an das Signal Registrierung passieren, und hat es wie ein schwacher Verweis verhalten (wenn das Signal Rückruf aufgerufen wird, wenn die ClipboardMonitor Instanz außerhalb des Bereichs ist, sollte es ein no-op sein).

Zusatz: phrasiert unabhängig von GObject oder GTK +:

Wie wollen Sie eine Callback-Methode zu einem undurchsichtigen Objekt bereitzustellen, mit WeakRef Semantik? Wenn das Verbindungsobjekt Gültigkeitsbereich verlässt, sollten sie gelöscht werden und der Rückruf als no-op handeln sollte; der Anschluss sollte nicht einen Verweis auf den Anschluss halten.

Zur Klarstellung: Ich möchte ausdrücklich vermeiden nennen, die eine „destructor / Finalizerthread“ Methode

War es hilfreich?

Lösung

Der üblicher Weg ist, das Signal zu trennen. Dies muss jedoch eine destructor artige Methode in der Klasse hat, explizit durch Code aufgerufen, die Ihr Objekt verwaltet. Dies ist notwendig, weil sonst werden Sie zirkuläre Abhängigkeit erhalten.

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)

Wie Sie wies darauf hin, müssen Sie weakrefs, wenn Sie zerstören vermeiden explicite wollen. Ich würde ein schwachen Rückruf Werk schreiben, wie:

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

(dies ist ein Proof of Concept-Code, funktioniert für mich - Sie sollten wahrscheinlich dieses Stück an Ihre Bedürfnisse anpassen). Ein paar Anmerkungen:

  • Ich Speicherung Callback-Objekt und Funktion separatelly. Sie können nicht einfach eine WeakRef einer gebundenen Methode machen, weil gebundene Methoden sehr temporäre Objekte sind. Eigentlich wird weakref.ref(obj.method) zerstört das gebundene Methode Objekt sofort nach einer WeakRef zu schaffen. Ich habe nicht überprüfen, ob es nötig ist auch eine WeakRef auf die Funktion zu speichern ... Ich denke, wenn Ihr Code statisch ist, werden Sie wahrscheinlich, dass zu vermeiden.
  • Die Objekt-Wrapper wird entfernen sich von dem Signalgeber, wenn es feststellt, dass die schwache Referenz aufgehört zu existieren. Dies ist auch notwendig, um die kreisförmige Abhängigkeit zwischen dem CallbackWrapper und dem Signalgeber Objekt zu zerstören.

Andere Tipps

(Diese Antwort verfolgt meine Arbeit)

Diese zweite Version wird auch trennen; Ich habe eine Komfortfunktion für gobjects, aber ich brauche eigentlich diese Klasse für einen allgemeineren Fall -. Sowohl für D-Bus-Signal-Rückrufe und GObject Rückrufe

Wie auch immer, was kann man nennen die WeakCallback Implementierung Stil? Es ist eine sehr saubere Kapselung des schwachen Rückruf, aber mit dem gobject / dbus Spezialisierung unnoticeably geheftet. Beats Schreiben zwei Unterklassen für diese beiden Fälle.

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)

nicht wirklich es noch versucht, aber:

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))
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top