为什么Django的信号处理默认使用弱引用作为回调函数?

24

Django文档在这个问题上有如下说明:

需要注意的是,Django默认将信号处理函数存储为弱引用,因此如果您的处理函数是一个本地函数,它可能会被垃圾回收。为了防止这种情况发生,请在调用信号的connect()方法时传递weak=False参数。

我没有找到任何关于为什么默认使用弱引用的正当理由,我也不理解为什么您会希望明确注册的信号会隐式消失。那么这里弱引用的用例是什么?为什么它是默认值?

我意识到在99%的情况下这可能并不重要,但显然我还有些不理解的地方,我想知道是否有什么“陷阱”潜伏着,可能会在将来折磨我。

2个回答

14

信号处理程序被存储为弱引用,以避免它们所引用的对象因为信号仍在传递而未被垃圾回收(例如在显式删除信号处理程序后)。


1
但是如果信号处理程序被明确断开连接,那么信号处理程序将不会被引用到任何地方,并且在这种情况下它不会阻止任何东西的垃圾回收。使用弱引用似乎只有在信号没有被明确断开连接时才会有所区别,如果您没有请求,为什么要断开信号呢? - Jason C
1
你说得对。如果没有弱引用,要让一个信号处理程序被垃圾回收,需要做两件事:删除信号处理程序并断开连接。这可能不是每个人都显而易见的,所以我认为这就是默认使用弱引用的原因。 - Luper Rouch
2
很抱歉我这么迟钝,但我还是不明白。我想我理解了你所说的“断开”信号的意思:在Signal实例上调用disconnect方法,并将处理程序作为参数传递。但是你所说的“删除”信号处理程序是什么意思? - Jason C
6
您的信号处理程序可以是一个对象的方法。如果对象超出范围(或您将其删除等),通常会假定它会被垃圾回收,但如果一个非弱引用信号连接到它,它将不会被回收。 - Luper Rouch
2
最后,我认为我的困惑源于与大多数人不同的期望......我认为在所有情况下都需要明确注销信号会更有意义,但显然弱引用行为有一些用例。 - Jason C
1
抱歉,我在回答中修正了一个大错误:“信号处理程序被存储为弱引用,以避免它们所引用的对象被垃圾回收[...]”。如果我引入了额外的混淆,很抱歉 :) - Luper Rouch

6

绑定方法会保留对它们所属的对象的引用(否则,它们无法填充 self,请参见Python文档)。考虑以下代码:

import gc
class SomeLargeObject(object):
    def on_foo(self): pass

slo = SomeLargeObject()
callbacks = [slo.on_foo]

print [o for o in gc.get_objects() if isinstance(o, SomeLargeObject)]
del slo
print [o for o in gc.get_objects() if isinstance(o, SomeLargeObject)]
callbacks = []
print [o for o in gc.get_objects() if isinstance(o, SomeLargeObject)]

输出结果:
[<__main__.SomeLargeObject object at 0x15001d0>]
[<__main__.SomeLargeObject object at 0x15001d0>]
[]

在保持回调函数的弱引用时,需要注意的一件重要事情是,不能直接对绑定方法进行弱引用,因为它们总是即兴创建的:

>>> class SomeLargeObject(object):
...  def on_foo(self): pass
>>> import weakref
>>> def report(o):
...  print "about to collect"
>>> slo = SomeLargeObject()
>>> #second argument: function that is called when weakref'ed object is finalized
>>> weakref.proxy(slo.on_foo, report)
about to collect
<weakproxy at 0x7f9abd3be208 to NoneType at 0x72ecc0>

我正在浏览Django的实现,发现确实特别努力地提供了绑定方法的弱引用。但是未绑定的方法呢?它们的引用在哪里保留?什么会触发它们的垃圾回收?当您删除定义它们的模块时呢? - agentofuser

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接