using python WeakSet to enable a callback functionality

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

Leave a Comment