using python WeakSet to enable a callback functionality
Question: I'm investigating if I can implement an easy callback functionality in python.
I thought I might be able to use weakref.WeakSet for this, but there is
clearly something I'm missing or have misunderstood. As you can see in the
code I first tried with a list of call back methods in 'ClassA' objects, but
realized that this would keep objects that have been added to the list of
callbacks alive. Instead I tried using weakref.WeakSet but that doesnt do the
trick either (at least not en this way). Comments in the last four lines of
code explain what I want to happen.

Can anyone help me with this?

    
    
    from weakref import WeakSet
    class ClassA:
        def __init__(self):
            #self.destroyCallback=[]
            self.destroyCallback=WeakSet()
        def __del__(self):
            print('ClassA object %d is being destroyed' %id(self))
            for f in self.destroyCallback:
                f(self)
    class ClassB:
        def destroyedObjectListener(self,obj):
            print('ClassB object %d is called because obj %d is being destroyed'%(id(self),id(obj)))
    a1=ClassA()
    a2=ClassA()
    b=ClassB()
    
    a1.destroyCallback.add(b.destroyedObjectListener)
    #a1.destroyCallback.append(b.destroyedObjectListener)
    print('destroyCallback len() of obj: %d is: %d'%(id(a1),len(a1.destroyCallback))) # should be 1
    
    a2.destroyCallback.add(b.destroyedObjectListener)
    #a2.destroyCallback.append(b.destroyedObjectListener)
    print('destroyCallback len() of obj: %d is: %d'%(id(a2),len(a2.destroyCallback))) # should be 1
    
    del a1 # Should call b.destroyedObjectListener(self) in its __del__ method
    
    del b # should result in no strong refs to b so a2's WeakSet should automatically remove added item
    
    print('destroyCallback len() of obj: %d is: %d'%(id(a2),len(a2.destroyCallback))) # should be 0
    del a2 # Should call __del__ method
    

UPDATE: solution based on the accepted answer can be found on github:
git@github.com:thgis/PythonEvent.git


Answer: You cannot create weak references to method objects. Method objects are _short
lived_ ; they are created on the fly as you access the name on the instance.
See the [descriptor howto](http://docs.python.org/2/howto/descriptor.html) how
that works.

When you access a method name, a _new_ method object is created for you, and
when you then add that method to the `WeakSet`, no other references exist to
it anymore, so garbage collection happily cleans it up again.

You'll have to store something less transient. Storing instance objects
themselves would work, then call a predefined method on the registered
callbacks:

    
    
    def __del__(self):
        for f in self.destroyCallback:
            f.destroyedObjectListener(self)
    

and to register:

    
    
    a1.destroyCallback.add(b)
    

You can also make `b` _itself_ a callable by giving it a `__call__` method:

    
    
    class ClassB:
        def __call__(self,obj):
            print('ClassB object %d is called because obj %d '
                  'is being destroyed' % (id(self), id(obj)))
    

Another approach would be to store a reference to the underlying function
object plus a reference to the instance:

    
    
    import weakref
    
    
    class ClassA:
        def __init__(self):
            self._callbacks = []
    
        def registerCallback(self, callback):
            try:
                # methods
                callback_ref = weakref.ref(callback.__func__), weakref.ref(callback.__self__)
            except AttributeError:
                callback_ref = weakref.ref(callback), None
            self._callbacks.append(callback_ref)
    
        def __del__(self):
            for callback_ref in self._callbacks:
                callback, arg = callback_ref[0](), callback_ref[1]
                if arg is not None:
                    # method
                    arg = arg()
                    if arg is None:
                        # instance is gone
                        continue
                    callback(arg, self)
                    continue
                else:
                    if callback is None:
                        # callback has been deleted already
                        continue
                    callback(self)
    

Demo:

    
    
    >>> class ClassB:
    ...     def listener(self, deleted):
    ...         print('ClassA {} was deleted, notified ClassB {}'.format(id(deleted), id(self)))
    ... 
    >>> def listener1(deleted):
    ...     print('ClassA {} was deleted, notified listener1'.format(id(deleted)))
    ... 
    >>> def listener2(deleted):
    ...     print('ClassA {} was deleted, notified listener2'.format(id(deleted)))
    ... 
    >>> # setup, one ClassA and 4 listeners (2 methods, 2 functions)
    ... 
    >>> a = ClassA()
    >>> b1 = ClassB()
    >>> b2 = ClassB()
    >>> a.registerCallback(b1.listener)
    >>> a.registerCallback(b2.listener)
    >>> a.registerCallback(listener1)
    >>> a.registerCallback(listener2)
    >>> 
    >>> # deletion, we delete one instance of ClassB, and one function
    ... 
    >>> del b1
    >>> del listener1
    >>> 
    >>> # Deleting the ClassA instance will only notify the listeners still remaining
    ... 
    >>> del a
    ClassA 4435440336 was deleted, notified ClassB 4435541648
    ClassA 4435440336 was deleted, notified listener2
    

