问题描述
我正在研究是否可以在 python 中实现一个简单的回调功能.我以为我可以为此使用weakref.WeakSet,但显然我遗漏或误解了某些东西.正如您在代码中看到的,我首先尝试使用ClassA"对象中的回调方法列表,但意识到这将使已添加到回调列表中的对象保持活动状态.相反,我尝试使用 weakref.WeakSet 但这也不起作用(至少不是这样).最后四行代码中的注释解释了我想要发生的事情.
谁能帮我解决这个问题?
from weakref import WeakSetA类:def __init__(self):#self.destroyCallback=[]self.destroyCallback=WeakSet()def __del__(self):print('ClassA 对象 %d 正在被销毁' %id(self))对于 self.destroyCallback 中的 f:f(自我)B类:def destroyObjectListener(self,obj):print('ClassB 对象 %d 被调用,因为 obj %d 正在被销毁'%(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))) # 应该是 1a2.destroyCallback.add(b.destroyedObjectListener)#a2.destroyCallback.append(b.destroyedObjectListener)print('destroyCallback len() of obj: %d is: %d'%(id(a2),len(a2.destroyCallback))) # 应该是 1del a1 # 应该在其 __del__ 方法中调用 b.destroyedObjectListener(self)del b # 应该不会导致对 b 的强引用,因此 a2 的 WeakSet 应该会自动删除添加的项目print('destroyCallback len() of obj: %d is: %d'%(id(a2),len(a2.destroyCallback))) # 应该是 0del a2 # 应该调用 __del__ 方法
更新:基于已接受答案的解决方案可以在 github 上找到:[email protected]:thgis/PythonEvent.git
您不能创建对方法对象的弱引用.方法对象是短暂的;它们是在您访问实例上的名称时动态创建的.请参阅描述符操作方法工作原理.>
当您访问一个方法名称时,会为您创建一个新方法对象,然后当您将该方法添加到 WeakSet
时,不存在对其的其他引用不再,所以垃圾收集高兴地再次清理它.
你必须存储一些不那么短暂的东西.存储实例对象本身会起作用,然后在注册的回调上调用预定义的方法:
def __del__(self):对于 self.destroyCallback 中的 f:f.destroyedObjectListener(self)
并注册:
a1.destroyCallback.add(b)
你也可以通过给它一个 __call__
方法使 b
itself 成为可调用的:
class ClassB:def __call__(self,obj):print('ClassB 对象 %d 被调用,因为 obj %d ''正在被销毁' % (id(self), id(obj)))
另一种方法是存储对底层函数对象的引用加上对实例的引用:
导入弱引用A类:def __init__(self):self._callbacks = []def registerCallback(self, callback):尝试:# 方法callback_ref = weakref.ref(callback.__func__), weakref.ref(callback.__self__)除了属性错误:callback_ref = weakref.ref(callback), 无self._callbacks.append(callback_ref)def __del__(self):对于 self._callbacks 中的 callback_ref:回调, arg = callback_ref[0](), callback_ref[1]如果 arg 不是 None:# 方法arg = arg()如果 arg 为 None:# 实例消失了继续回调(参数,自我)继续别的:如果回调为无:#回调已经被删除继续回调(自我)
演示:
>>>B类:...定义监听器(自我,已删除):... print('ClassA {} 被删除,通知 ClassB {}'.format(id(deleted), id(self)))...>>>def listener1(已删除):... print('ClassA {} 被删除,通知 listener1'.format(id(deleted)))...>>>def listener2(已删除):... print('ClassA {} 被删除,通知listener2'.format(id(deleted)))...>>># 设置,一个 ClassA 和 4 个监听器(2 个方法,2 个函数)...>>>a = ClassA()>>>b1 = ClassB()>>>b2 = ClassB()>>>a.registerCallback(b1.listener)>>>a.registerCallback(b2.listener)>>>a.registerCallback(listener1)>>>a.registerCallback(listener2)>>>>>>#删除,我们删除ClassB的一个实例和一个函数...>>>德尔 b1>>>删除监听器1>>>>>># 删除 ClassA 实例只会通知剩余的监听器...>>>德尔阿ClassA 4435440336 被删除,通知ClassB 4435541648ClassA 4435440336 被删除,通知listener2I'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: [email protected]:thgis/PythonEvent.git
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
这篇关于使用 python WeakSet 启用回调功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!