GNOME Bugzilla – Bug 154845
Provide a way to lookup GClosure from a callable PyObject
Last modified: 2006-04-29 18:50:43 UTC
Wrap this as a method on GObject so that callables may be disconnected without keeping the connection id around. It would be nice to specify some of the match options with keyword args so something like the following is possible. def handler(*args): return None obj.connect('notify', handler) obj.disconnect_matched(func = handler) Implementing this is nontrivial because it requires some way to get from the Python callable to the closure. Callables should be compared using Python equality semantics so that two different instance method objects for the same Python instance and method are considered to be equal.
There doesn't seem to be a way to do this, since you can't just get a list of all the connected closures. If we stuffed function name into the data member of GClosure, you might be able to do a data lookup to find the matching handlers. However, even this will have some problems if people want to connect methods as signal handlers (which isn't uncommon). Testing for pointer equality isn't enough to determine if two method objects are the same: >>> class C(object): ... def method(self): pass ... >>> c = C() >>> f1 = c.method >>> f2 = c.method >>> f1 == f2 True >>> id(f1) == id(f2) False This is because Python creates a new method object each time you ask for the function "method" in the context of the instance "c".
However, you can still attach things to a function objects dictionary, that's a place where the GClosure can be stored. Tried to do that as quick hack yesterday (using PyCObject_FromVoidPtr) and it actually seemed to work. The downside is that you can't filter on arguments though. I'm not sure what should be done if multiple signals are connected to the same func, perhaps storing a it in a hashtable (not that hash() for a method always returns the same value)
Given that hash values are not guaranteed to be unique, I'd feel a bit uncomfortable doing "delete all connections where the handler hashes to hash(X)" when the user asks to "delete all connections where the handler is X". Perhaps this is a case where a new libgobject API is needed. I'm sure Python isn't the only language that runs into this problem.
So, what about a list on each method type (since it's __dict__ seems to be persistent), with all the closures that's attached to the current object? When doing connect, it'd do something like this: if not func.__dict.__.has_key('closures'): closures = func.__dict__['closures'] = [] else: closures = [] closure.append(pygclosure) and disconnect, disconnect_matched would just remove it from the list.
Isn't a list of the python objects connected to a signal kept so it can be traversed when the gc runs? If so, can't disconnect_matched simply look in the list? I don't think disconnect_matched needs to work in O(1) time (in fact it doesn't at the C level)
I had forgotten about the closure list. That should make it possible to implement any style of disconnect routine. It'd probably end up being O(n^2) time in the worst case (scan object->closures, call disconnect_matched() for matching closures), but that shouldn't be a big deal since the number of handlers connected isn't usually large, and it isn't used that often.
Confirming and updating summary. The following functions will be easy to wrap once this is fixed: g_signal_handlers_block_matched g_signal_handlers_unblock_matched g_signal_handlers_disconnect_matched However, they are quite lowlevel, so what we should have is: GObject.block_by_func() GObject.unblock_by_func() GObject.disconnect_by_func() It's also possible to search for closures and data, but it doesn't seem very relevant from Python. g_signal_handler_find could also be wrapped, but it's probably not too useful.
This would be particularly useful when working with libglade, since glade_xml_signal_autoconnect() et al throw away the connection ids, so any signals you want to be able to block need to be connected by hand.
It proved to be pretty easy to fix. I added three new methods to gobject.GObject: GObject.disconnect_by_func() GObject.handler_block_by_func() GObject.handler_unblock_by_func() Checking in gobject/pygobject.c; /cvs/gnome/gnome-python/pygobject/gobject/pygobject.c,v <-- pygobject.c new revision: 1.69; previous revision: 1.68 done Checking in gobject/pygtype.c; /cvs/gnome/gnome-python/pygobject/gobject/pygtype.c,v <-- pygtype.c new revision: 1.49; previous revision: 1.48 done Checking in tests/test_signal.py; /cvs/gnome/gnome-python/pygobject/tests/test_signal.py,v <-- test_signal.py new revision: 1.10; previous revision: 1.9 done