Design thought for callbacks

Ian Kelly ian.g.kelly at gmail.com
Mon Mar 2 10:34:15 EST 2015


On Mon, Mar 2, 2015 at 4:04 AM, Cem Karan <cfkaran2 at gmail.com> wrote:
> On Feb 26, 2015, at 2:54 PM, Ian Kelly <ian.g.kelly at gmail.com> wrote:
>> On Feb 26, 2015 4:00 AM, "Cem Karan" <cfkaran2 at gmail.com> wrote:
>> >
>> >
>> > On Feb 26, 2015, at 12:36 AM, Gregory Ewing <greg.ewing at canterbury.ac.nz> wrote:
>> >
>> > > Cem Karan wrote:
>> > >> I think I see what you're talking about now.  Does WeakMethod
>> > >> (https://docs.python.org/3/library/weakref.html#weakref.WeakMethod) solve
>> > >> this problem?
>> > >
>> > > Yes, that looks like it would work.
>> >
>> >
>> > Cool!
>>
>> Sometimes I wonder whether anybody reads my posts. I suggested a solution involving WeakMethod four days ago that additionally extends the concept to non-method callbacks (requiring a small amount of extra effort from the client in those cases, but I think that is unavoidable. There is no way that the framework can determine the appropriate lifetime for a closure-based callback.)
>
> I apologize about taking so long to reply to everyone's posts, but I've been busy at home.
>
> Ian, it took me a while to do some research to understand WHY what you were suggesting was important; you're right about storing the object as well as the method/function separately, but I think that WeakMethod might solve that completely, correct?  Are there any cases where WeakMethod wouldn't work?

WeakMethod only works for bound method objects. If you pass it a
non-method function, you'll get a TypeError:

>>> from weakref import WeakMethod
>>> WeakMethod(lambda: None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.4/weakref.py", line 49, in __new__
    .format(type(meth))) from None
TypeError: argument should be a bound method, not <class 'function'>

This check uses duck typing, so you could perhaps write a method-like
class with __self__ and __func__ attributes and pass that to the
WeakMethod constructor in the case of non-methods. There's a bigger
issue with this however, which is that WeakMethod works by keeping
weak references to *both* the object and the function, meaning that as
soon as the function has no other references, the WeakMethod expires
even if the object is still alive. This isn't a problem for methods
because it's the transience of the method object, not the underlying
function, that WeakMethod seeks to work around. But it doesn't by
itself do anything to solve the problem of closures or lambdas that
may themselves be transient.

Revisiting the implementation I suggested previously, I want to make a
correction. This would be better solved with a WeakValueDictionary:

class Listenable:
    def __init__(self):
        self._callbacks = weakref.WeakValueDictionary()

    def listen(self, callback, owner=None):
        if owner is None:
            if isinstance(callback, types.MethodType):
                owner = weakref.WeakMethod(callback)
            else:
                owner = callback
        # TODO: Should anything happen if the callback is already in the dict?
        self._callbacks[callback] = owner

    def do_callbacks(self, message):
        for callback in self._callbacks.keys():
            callback(message)

This approach has two benefits over the previous one: it simplifies
the callback management a bit, and it avoids making the assumption
that the owner is hashable (it assumes instead that the callback is
hashable, but I think that's reasonable).



More information about the Python-list mailing list