Welcome, guest | Sign In | My Account | Store | Cart

This simple class implements the observer design pattern. Acting as a registration hub, it fires simple Events when requested.

Python, 59 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from weakref import ref

class Event(object):
    def __init__(self, name, stuff=None):
        self.name = name
        self.stuff = stuff

    def __str__(self):
        return '%s(%s)'%(self.name,self.stuff)

class Observer(object):
    listeners={}
    weakListeners={}
    
    def register(self, eventName, callback, isWeak=False):
        if not isWeak: listenDict = Observer.listeners
        else: listenDict = Observer.weakListeners
        callbacks = listenDict.get(eventName)
        if not callbacks:
            callbacks = []
            listenDict[eventName] = callbacks
        if not isWeak and callback not in callbacks:
            callbacks.append(callback)
        elif isWeak:
            for cb in callbacks:
                refobj = cb()
                if refobj and refobj==callback:
                    return
            callbacks.append(ref(callback))

    def unregister(self, eventName, callback, isWeak=False):
        if not isWeak: listenDict = Observer.listeners
        else: listenDict = Observer.weakListeners
        callbacks = listenDict.get(eventName)
        if callbacks:
            if not isWeak:
                try: callbacks.remove(callback)
                except: pass
            else:
                cblist = []; cblist.extend(callbacks)
                for cb in cblist:
                    refobj = cb()
                    if refobj and refobj==callback:
                        callbacks.remove(cb)
                    elif not refobj:
                        callbacks.remove(cb)

    def fireEvent(self, event):
        if not event: return
        callbacks = []
        callbacks.extend(Observer.listeners.get(event.name,[]))
        for cb in Observer.weakListeners.get(event.name,[]): callbacks.append(cb())
        for cb in callbacks:
            if cb: cb(event)

    def fireEventName(self, name, stuff=None):
        self.fireEvent(Event(name,stuff))

observer = Observer()

I use this to send application event notifications between system components (not distributed and synchronized processing. Its works fine for that purpose. It doesn't account for threads, so you has to watch inter-thread communication carefully (as usual :-)

UPDATE: I added support for weak references.

6 comments

Zoran Isailovski 18 years, 10 months ago  # | flag
hemanth sethuram 18 years, 10 months ago  # | flag

Why callbacks list in fireEvent function. I am not able to understand why 'callbacks' list is needed in fireEvent function. Instead you can just have it like this:

cbs = Observer.listeners.get(event.name)

in register you are guaranteeing a callback list for the event, so you

either get None or a list on the above statement.

if cbs:

for cb in cbs:

    cb(event)

--Hemanth

Chris Arndt 18 years, 9 months ago  # | flag

Misnomer. I'd rather call this class an "event dispatcher", because it is independent of the objects that actually fire the events (probably because they changed their state). Objects subscribed to the dispatcher would then have to know for themselves, what the semantics of the event they received are, i.e what/who has actually changed.

The term observer/observable or publisher/subscriber is usually used for a pair of classes that define a similar but somewhat different interface, as the observable (publisher) class is usually used as a base class for objects, wanting to notify others about changes in their _own_ state. These objects would normally correspond to the "model" part of an MVC pattern.

The observer (subscriber) only needs to provide a method (usually called "update") that is called whenever the publisher fires an event. It then either has to ask the publisher about it's new state or the new state is already provided when update() is called ("pull" resp. "push").

Recipe 131499 mentioned by the first comment implements the technique I described.

Jonathan Kolyer (author) 18 years, 6 months ago  # | flag

Need the callbacks list due to possible looping. If the callback invoked changes the list of registered objects, you will need a copy of the list. Otherwise some callbacks may not be invoked. This is the case when you register for an event, then unregister when the event occurs.

Jonathan Kolyer (author) 18 years, 6 months ago  # | flag

Gotcha. Point taken. For my purposes, this was an simple implementation for a specific design need. The semantics and subtlies wheren't given much thought.

Timothee Besset 15 years, 6 months ago  # | flag

Watch out, the weak references support is treacherous. Bound methods won't work with weak refs, see http://code.activestate.com/recipes/81253/

Created by Jonathan Kolyer on Thu, 26 May 2005 (PSF)
Python recipes (4591)
Jonathan Kolyer's recipes (6)

Required Modules

Other Information and Tasks