using python WeakSet to enable a callback functionality

前端 未结 2 1495
遇见更好的自我
遇见更好的自我 2020-12-06 11:09

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

相关标签:
2条回答
  • 2020-12-06 11:19

    Try the following changes:

    To update the WeakSet:

    a1.destroyCallback.add(b)

    so the WeakSet holds a reference to b.

    Then in the __del__ method of ClassA, trigger the callback like this:

    for f in self.destroyCallback:
            f.destroyedObjectListener(self)
    
    0 讨论(0)
  • 2020-12-06 11:21

    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
    
    0 讨论(0)
提交回复
热议问题