【问题标题】:using python WeakSet to enable a callback functionality使用 python WeakSet 启用回调功能
【发布时间】:2014-03-16 14:36:18
【问题描述】:

我正在研究是否可以在 python 中实现简单的回调功能。我想我也许可以为此使用weakref.WeakSet,但显然我遗漏了一些东西或误解了一些东西。正如您在代码中看到的那样,我首先尝试使用“ClassA”对象中的回调方法列表,但意识到这会使已添加到回调列表中的对象保持活动状态。相反,我尝试使用 weakref.WeakSet 但这也不起作用(至少不是这样)。最后四行代码中的注释解释了我想要发生的事情。

谁能帮我解决这个问题?

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

更新:基于已接受答案的解决方案可以在 github 上找到:git@github.com:thgis/PythonEvent.git

【问题讨论】:

    标签: python callback destructor weak-references


    【解决方案1】:

    您不能创建对方法对象的弱引用。方法对象是短暂的;它们是在您访问实例上的名称时动态创建的。请参阅descriptor howto 是如何工作的。

    当您访问一个方法名称时,会为您创建一个 new 方法对象,然后当您将该方法添加到 WeakSet 时,不再存在对它的其他引用,因此垃圾回收愉快地再次清理它。

    您必须存储一些不那么短暂的东西。存储实例对象本身会起作用,然后在注册的回调上调用预定义的方法:

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

    并注册:

    a1.destroyCallback.add(b)
    

    您还可以通过给__call__ 方法提供__call__ 方法,使b 自身成为可调用对象:

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

    另一种方法是存储对底层函数对象的引用以及对实例的引用:

    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)
    

    演示:

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

    【讨论】:

    • 我现在收到了您的建议,它似乎很有效。我已经将解决​​方案封装在一个 Event 类中,并且只实现了 add()、remove() 和 signal() 以尽量减少使用。也许它会扩大。如果有人有兴趣,可以使用以下方式从 github 下载解决方案:git@github.com:thgis/PythonEvent.git
    • 好东西!我建议的唯一一点改进是使用getattr(callback, '__self__', None) 而不是异常。
    • @ShitalShah:不,因为你创建了一个对None 的弱引用。我们要存储None 自身,而不是对其的弱引用。这样你就可以检测出方法和函数之间的区别。
    【解决方案2】:

    尝试以下更改:

    要更新 WeakSet:

    a1.destroyCallback.add(b)

    所以 WeakSet 持有对 b 的引用。

    然后在ClassA的__del__方法中,像这样触发回调:

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

    【讨论】:

    • 感谢您的回复格伦。我想这会起作用..我想我只能为对象创建一个弱引用,而不是我尝试过的对象方法。您的方式将为 b 创建一个弱引用,因此可以工作,但必须显式编写函数名称“destroyedObjectListener”,事件驱动的想法就消失了。但是你让我得出的结论是,问题在于我试图为类方法创建一个弱引用。
    猜你喜欢
    • 1970-01-01
    • 2020-06-28
    • 1970-01-01
    • 2021-01-08
    • 1970-01-01
    • 2011-06-19
    • 2021-04-30
    • 2021-02-28
    • 2014-01-18
    相关资源
    最近更新 更多