Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't the weakref work on this bound method?

I have a project where i'm trying to use weakrefs with callbacks, and I don't understand what I'm doing wrong. I have created simplified test that shows the exact behavior i'm confused with.

Why is it that in this test test_a works as expected, but the weakref for self.MyCallbackB disappears between the class initialization and calling test_b? I thought like as long as the instance (a) exists, the reference to self.MyCallbackB should exist, but it doesn't.

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()
like image 770
Asa Ayers Avatar asked Mar 01 '09 07:03

Asa Ayers


2 Answers

The other answers address the why in the original question, but either don't provide a workaround or refer to external sites.

After working through several other posts on StackExchange on this topic, many of which are marked as duplicates of this question, I finally came to a succinct workaround. When I know the nature of the object I'm dealing with, I use the weakref module; when I might instead be dealing with a bound method (as occurs in my code when using event callbacks), I now use the following WeakRef class as a direct replacement for weakref.ref(). I've tested this with Python 2.4 through and including Python 2.7, but not on 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__)
like image 171
Kyle Avatar answered Oct 16 '22 05:10

Kyle


You want a WeakMethod.

An explanation why your solution doesn't work can be found in the discussion of the recipe:

Normal weakref.refs to bound methods don't quite work the way one expects, because bound methods are first-class objects; weakrefs to bound methods are dead-on-arrival unless some other strong reference to the same bound method exists.

like image 45
Pankrat Avatar answered Oct 16 '22 04:10

Pankrat