5

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

A bit of context:

I was trying to implement an Listener (or Observer, same thing) pattern: An EventManager keeps a list of all the Listeners handlers interested in an Event. For example, a Listener object would have a onEndOfTheWorldEvent method which would be called by the EventManager each time an instance of the event class EndOfTheWorldEvent is posted. Easy.

Except that I wanted to weak reference the handlers because I don't want the EventManager to keep my handlers (bound methods) alive when the Listener was not needed anymore.

So I thought "Let's throw all the handlers in a WeakSet". I couldn't get it to work.

I dump here the code (or what's left of it when I reduce it to the minimum, here there's only one type of event and only one type of handler).

#! /usr/bin/python
"""

"""
import sys
import weakref

class Listener(object):
    def handler(self, event):
        print event

class EventManager(object):
    def __init__(self):
        self.handlers = weakref.WeakSet()
    def register(self, listener):
        print "Registering..."
        self.handlers.add(listener.handler)
        CountRefs(listener.handler)
        print "Number of handlers registered:", len(self.handlers)
        print "Registered."

def CountRefs(what):
    print "Hard count:", sys.getrefcount(what)
    print "Weak count:", weakref.getweakrefcount(what)

listener = Listener()
em = EventManager()
CountRefs(listener.handler)
em.register(listener)
CountRefs(listener.handler)

result:

Hard count: 3
Weak count: 0
Registering...
Hard count: 3
Weak count: 0
Number of handlers registered: 0
Registered.
Hard count: 3
Weak count: 0

It just looks like there's never any weak reference, and the set remains empty.

To make it even simpler:

>>> class C(object):
>>>     def blah(self):
>>>         print "blah"
>>> 
>>> c = C()
>>> w = weakref.ref(c.blah)
>>> print w
<weakref at 0x11e59f0; dead>

Can't I create weakrefs to methods at all ? If not, why not ?

So I guess a workaround would be to replace the WeakSet with a WeakKeyDictionary: key is the listener itself, and value the handler. Indeed I can weakref my Listeners. But it makes the data structure a bit more complicated, and when comes the time to broadcast the events to everybody, there's one more level in that structure to go through.

What do you think ?

Community
  • 1
  • 1
Niriel
  • 2,605
  • 3
  • 29
  • 36
  • This question is not really duplicate, because it focuses on a solution and not just "why doesn't". Let alone the good accepted answer here. At best you can mark the other question as duplicate in the sense of: "this one implies the answer to the other". – kxr Apr 10 '17 at 16:05

2 Answers2

8

Let's say you want weakrefs on a method "meth".

You can get weakrefs on it like this

weak_obj = weakref.ref(meth.im_self)
weak_func = weakref.ref(meth.im_func)

So, you can deref it like that

obj = weak_obj()
func = weak_func()

and get "meth" back with

meth = getattr(obj, func.__name__)
dugres
  • 12,613
  • 8
  • 46
  • 51
  • Wow, neat trick. I never needed to look at these im_self and co before. – Niriel Aug 07 '11 at 21:45
  • Actually, I needed that, because the bound method references the instance, and therefore the instance does not disappear from the WeakKeyDictionary. Thanks ! – Niriel Aug 08 '11 at 15:05
  • 3
    Seems that from Python 2.6 moving forward the preferred access for these is `bound_method.__self__` for the object, and `bound_method.__func__` for the function. –  Apr 20 '14 at 15:01
2

listener.handler gives you a new bound reference to the function each time. So it gets garbage collected almost immediately.

Neil
  • 54,642
  • 8
  • 60
  • 72
  • Indeed, the comparison with "is" always return False. Then again I thought it could just have been a weird convention. – Niriel Aug 07 '11 at 21:40
  • @Neil Could you please elaborate more on `listener.handler` and how to use it to give a new bound reference to a function? Thanks in advance! – AhmedWas Mar 15 '19 at 14:37
  • @AhmedWas I'm not sure what you're asking. `listener.handler` is an expression that returns a function which invokes the original function (as declared in the class) but always bound to the given instance. It just happens that the expression works by giving you a new bound reference each time, which is unhelpful in the context of the original question. – Neil Mar 15 '19 at 22:47