weakref pitfall

Peter Otten __peter__ at web.de
Sat Oct 20 17:00:46 CEST 2007


Odalrick wrote:

> I'm trying to write a simple game and decided I need an eventmanager.
> 
> <code>
> import weakref
> from collections import defaultdict
> 
> class _EventManager( object ):
>     def __init__( self ):
>         self._handled_events =
> defaultdict( weakref.WeakKeyDictionary )
> 
>     def register( self, handler, event_type, filter=None ):
>         self._handled_events[event_type][handler] = filter
> 
>     def deregister( self, handler, event_type ):
>         self._handled_events[event_type].pop( handler, None  )
> 
>     def handle_event( self, event ):
>         event_type = event.type
>         for handler, filter in
> self._handled_events[event_type].items():
>             if filter == None or filter(event):
>                 handler( event )
> 
> eventmanager = _EventManager()
> 
> __all__ = [ eventmanager ]
> </code>
> 
> Fairly simple, yet there was some strange bug that prevented my game
> from exiting.
> 
> I think what happened was that when __init__ ends, self goes out of
> scope, and by extension so does self.handle_quit, Now there are no
> more refeences to self.handle_quit, because weakrefs don't count, and
> the event gets automatically dropped from my eventmanager. I thought
> that wouldn't happen because handle_quit is part of the class and
> instance MainGame.

No, self is yet another reference to the _EventManager instance which
survives the __init__() call because it is also referenced by the
global variable eventmanager.
On the other hand, self.handle_quit creates a new bound-method object every
time which doesn't even live as long as __init__().
 
> Am I right in my guess? If so, how should I adress this bug? If not,
> what is the bug?

The easiest option, to forget about weakrefs, seems to be the best
here. The other option is to keep a reference to the bound method, e. g.:

class MainGame(object):
    def __init__(self, ...):
        # put a bound method into the MainGame instance
        # you can use a different attribute name if you want
        hq = self.handle_quit = self.handle_quit
        # register it
        eventmanager.register(hq, ...)

Peter



More information about the Python-list mailing list