A cleaner way of handling events (and also much faster, but perhaps consuming a bit more memory) is to have multiple event handlers in your code. Something like that:
Desired Interface
class KeyboardEvent: pass class MouseEvent: pass class NotifyThisClass: def __init__(self, event_dispatcher): self.ed = event_dispatcher self.ed.add(KeyboardEvent, self.on_keyboard_event) self.ed.add(MouseEvent, self.on_mouse_event) def __del__(self): self.ed.remove(KeyboardEvent, self.on_keyboard_event) self.ed.remove(MouseEvent, self.on_mouse_event) def on_keyboard_event(self, event): pass def on_mouse_event(self, event): pass
Here the __init__ method takes an EventDispatcher as an argument. The EventDispatcher.add function now accepts the type of event and listener you are interested in.
This has advantages for efficiency, because the listener only ever receives challenges for the events of interest to us. This also leads to more general code inside the EventDispatcher itself:
EventDispatcher Implementation
class EventDispatcher: def __init__(self): # Dict that maps event types to lists of listeners self._listeners = dict() def add(self, eventcls, listener): self._listeners.setdefault(eventcls, list()).append(listener) def post(self, event): try: for listener in self._listeners[event.__class__]: listener(event) except KeyError: pass # No listener interested in this event
But the problem is with this implementation. Inside NotifyThisClass you do the following:
self.ed.add(KeyboardEvent, self.on_keyboard_event)
The problem is self.on_keyboard_event : this is the related method that you passed to EventDispatcher . Related methods contain a reference to self ; this means that as long as the EventDispatcher has an associated method, self will not be deleted.
WeakBoundMethod
You will need to create a WeakBoundMethod class containing only a weak reference to self (I see that you already know about weak links), so that EventDispatcher does not prevent the removal of self .
An alternative would be to have the NotifyThisClass.remove_listeners function, which you call before deleting the object, but this is not a very clean solution, and I find it very error prone (easy to forget).
The WeakBoundMethod implementation will look something like this:
class WeakBoundMethod: def __init__(self, meth): self._self = weakref.ref(meth.__self__) self._func = meth.__func__ def __call__(self, *args, **kwargs): self._func(self._self(), *args, **kwargs)
Here's a more robust implementation I posted in CodeReview, and here is an example of how you will use the class:
from weak_bound_method import WeakBoundMethod as Wbm class NotifyThisClass: def __init__(self, event_dispatcher): self.ed = event_dispatcher self.ed.add(KeyboardEvent, Wbm(self.on_keyboard_event)) self.ed.add(MouseEvent, Wbm(self.on_mouse_event))
Connection Objects (optional)
When removing listeners from dispatcher / dispatcher, instead of using EventDispatcher useless to search for listeners until it finds the correct type of event, then search the list until it finds the correct listener, you may have something like this
class NotifyThisClass: def __init__(self, event_dispatcher): self.ed = event_dispatcher self._connections = [ self.ed.add(KeyboardEvent, Wbm(self.on_keyboard_event)), self.ed.add(MouseEvent, Wbm(self.on_mouse_event)) ]
Here, EventDispatcher.add returns a Connection object that knows where the dict lists in which it is located in the EventDispatcher . When the NotifyThisClass object NotifyThisClass deleted, that is, self._connections , which will call Connection.__del__ , which will remove the listener from the EventDispatcher .
This can make your code faster and easier to use, since you only need to explicitly add functions, they are automatically deleted, but you decide whether you want to do this. If you do, note that EventDispatcher.remove should no longer exist.