弱引用回调因循环引用未被调用

3 投票
3 回答
1629 浏览
提问于 2025-04-15 19:28

我正在尝试为有循环引用的Python类编写一个清理函数。我发现使用弱引用回调是个不错的选择。可惜的是,我用作回调的lambda函数似乎从来没有被调用。例如,运行以下代码:

def del_A(name):
    print('An A deleted:' + name)

class A(object):
    def __init__(self, name):
        print('A created')
        self.name = name
        self._wr = weakref.ref(self, lambda wr, n = self.name: del_A(n))

class B(object):
    def __init__(self):
        print('B created')

if __name__ == '__main__':
    a = A('a1')
    b = B()
    a.other = b
    b.other = a

返回:

A created
B created

去掉循环引用后,lambda回调就能正常工作了(会打印出'An A deleted: a1')。如果把lambda换成一个简单的函数调用也可以正常工作,但在初始化弱引用时,参数值是固定的,而不是在调用回调时才确定:

self._wr = weakref.ref(self, del_A(self.name))
...
a = A('a1')
a.name = 'a2'
b = B()
a.other = b
b.other = a

返回:

A created
An A deleted:a1
B created

有没有人知道为什么lambda回调在循环引用的情况下不起作用呢?

3 个回答

0

循环引用会被自动清理。虽然有一些例外,比如那些定义了 __del__ 方法的类。

通常情况下,你不需要自己定义 __del__ 方法。

3

当你使用

 self._wr = weakref.ref(self, lambda wr, n = self.name: del_A(n))  

这个回调函数只有在 self 快要被销毁的时候才会被调用。

回调函数没有被调用的原因是因为

a = A('a1')
b = B()
a.other = b   # This gives a another attribute; it does not switch `a` away from the original `a`
b.other = a

并没有让 a 被销毁。原来的 a 依然存在。

如果你把代码改成

a = A('a1')
b = B()
a = b
b = a

那么回调函数就会被调用。

当你使用

self._wr = weakref.ref(self, del_A(self.name))

时,你的回调函数变成了 Nonedel_A(self.name) 不是一个函数的引用,而是一个函数的调用。也就是说,del_A(self.name) 会立即打印出 An A deleted:a1(在 a1 真的被销毁之前),并返回 None,这就成了弱引用的默认回调。

3

我终于找到了为什么在弱引用的情况下回调函数不被调用的原因:

如果“弱引用对象在它引用的对象之前就消亡了”,那么弱引用的回调就不会被调用。

看起来,当循环引用被删除时,类A的弱引用属性会在回调有机会被调用之前就被删除了。一个解决办法是把终结器(也就是弱引用和它的回调)添加到一个终结器列表中。例如:

def del_A(name):
    print('An A deleted:' + name)

class A(object):
    def __init__(self, name, finalizers):
        print('A created')
        self.name = name
        finalizers.append(weakref.ref(self, lambda wr, n = self.name: del_A(n)))

class B(object):
    def __init__(self):
        print('B created')

def do_work(finalizers):
    a = A('a1', finalizers)
    b = B()
    a.other = b
    b.other = a

if __name__ == '__main__':
    finalizers = []
    do_work(finalizers)

这段代码会打印:

A created
B created
An A deleted:a1

需要注意的是,do_work()是必须的,否则终结器会在回调有机会被调用之前就被删除了。显然,终结器需要好好管理,以避免生成一个庞大的弱引用列表,但这又是另一个问题。

撰写回答