为什么弱引用在这个绑定方法上不起作用?

12

我有一个项目,尝试使用带回调函数的弱引用,但我不明白我做错了什么。我创建了一个简化的测试,展示了我困惑的确切行为。

为什么在这个测试中test_a按预期工作,但是self.MyCallbackB的弱引用在类初始化和调用test_b之间消失了呢?我原本认为只要实例(a)存在,对self.MyCallbackB的引用就应该存在,但实际上并没有。

import weakref

class A(object):
    def __init__(self):

        def MyCallbackA():
            print 'MyCallbackA'
        self.MyCallbackA = MyCallbackA

        self._testA = weakref.proxy(self.MyCallbackA)
        self._testB = weakref.proxy(self.MyCallbackB)

    def MyCallbackB(self):
        print 'MyCallbackB'

    def test_a(self):
        self._testA()

    def test_b(self):
        self._testB()

if __name__ == '__main__':
    a = A()    
    a.test_a()
    a.test_b()
3个回答

13

您想要一个WeakMethod

为什么您的解决方案不起作用的解释可以在该配方的讨论中找到:

普通的弱引用无法正常使用绑定方法,因为绑定方法是一级对象; 对绑定方法进行弱引用会立即失效,除非存在对同一绑定方法的其他强引用。


4
根据Weakref模块的文档:
在以下内容中,“引用对象”指被弱引用所引用的对象。
仅有弱引用不能保持对象存活:当对于引用对象唯一剩余的引用为弱引用时,垃圾回收机制可以销毁该引用对象并将其内存重用于其他用途。
MyCallbackA发生的情况是,在A的实例中你正在持有对它的引用,感谢-
self.MyCallbackA = MyCallbackA

现在,你的代码中没有对MyCallbackB绑定方法的引用。它只是作为一个未绑定的方法保存在a.__class__.__dict__中。基本上,当你执行self.methodName时,会创建一个绑定方法(并返回给你)。据我所知,绑定方法的工作方式类似于属性 - 使用描述符(只读):至少对于新式类。我确定,对于旧式类,会发生类似的事情,即没有描述符。我将把验证有关旧式类的声明留给更有经验的人。因此,一旦创建了弱引用,self.MyCallbackB就会消失,因为它没有强引用!

我的结论基于:

import weakref

#Trace is called when the object is deleted! - see weakref docs.
def trace(x):
    print "Del MycallbackB"

class A(object):
    def __init__(self):

        def MyCallbackA():
            print 'MyCallbackA'
        self.MyCallbackA = MyCallbackA
        self._testA = weakref.proxy(self.MyCallbackA)
        print "Create MyCallbackB"
        # To fix it, do -
        # self.MyCallbackB = self.MyCallBackB
        # The name on the LHS could be anything, even foo!
        self._testB = weakref.proxy(self.MyCallbackB, trace)
        print "Done playing with MyCallbackB"

    def MyCallbackB(self):
        print 'MyCallbackB'

    def test_a(self):
        self._testA()

    def test_b(self):
        self._testB()

if __name__ == '__main__':
    a = A()  
    #print a.__class__.__dict__["MyCallbackB"] 
    a.test_a()

输出

创建 MyCallbackB
删除 MycallbackB
完成与 MyCallbackB 的互动
MyCallbackA

注意:
我尝试对旧式类进行验证。结果发现,“print a.test_a.__get__”输出-

<method-wrapper '__get__' of instancemethod object at 0xb7d7ffcc>

对于新式和旧式类都是如此。因此,它可能并不真正是一个描述符,只是类似于描述符的东西。无论如何,重点是当您通过 self 访问实例方法时,将创建绑定方法对象,除非您保持对其的强引用,否则它将被删除。


4
其他答案已经回答了原问题中的“为什么”,但要么没有提供解决方法,要么引用了外部网站。
在浏览StackExchange上关于这个主题的其他帖子后(其中许多被标记为此问题的复制),我最终找到了一个简洁的解决方法。当我知道我正在处理的对象的性质时,我使用weakref模块; 当我可能会处理绑定方法时(当使用事件回调时出现这种情况),我现在使用以下WeakRef类作为weakref.ref()的直接替代品。我已经测试过Python 2.4到包括Python 2.7,但没有在Python 3.x上测试过。
class WeakRef:

    def __init__ (self, item):

        try:
            self.method   = weakref.ref (item.im_func)
            self.instance = weakref.ref (item.im_self)

        except AttributeError:
            self.reference = weakref.ref (item)

        else:
            self.reference = None


    def __call__ (self):

        if self.reference != None:
            return self.reference ()

        instance = self.instance ()

        if instance == None:
            return None

        method = self.method ()

        return getattr (instance, method.__name__)

3
对于Python 3,请将“item.im_func”替换为“item.__func__”,将“item.im_self”替换为“item.__self__”。 - lou
可能需要“def eq(self, other): return other() == self()”吗?这是最好的比较模式吗?(也可能需要“__ne__”作为反向操作) - Rafe

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