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

This is a Python implementation of the observer pattern described by Gamma et. al. It defines a one-to many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

The example should output: Setting Data 1 = 10 DecimalViewer: Subject Data 1 has data 10 HexViewer: Subject Data 1 has data 0xa Setting Data 2 = 15 HexViewer: Subject Data 2 has data 0xf DecimalViewer: Subject Data 2 has data 15 Setting Data 1 = 3 DecimalViewer: Subject Data 1 has data 3 HexViewer: Subject Data 1 has data 0x3 Setting Data 2 = 5 HexViewer: Subject Data 2 has data 0x5 DecimalViewer: Subject Data 2 has data 5 Detach HexViewer from data1 and data2. Setting Data 1 = 10 DecimalViewer: Subject Data 1 has data 10 Setting Data 2 = 15 DecimalViewer: Subject Data 2 has data 15

Python, 74 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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
class Subject:
    def __init__(self):
        self._observers = []

    def attach(self, observer):
        if not observer in self._observers:
            self._observers.append(observer)

    def detach(self, observer):
        try:
            self._observers.remove(observer)
        except ValueError:
            pass

    def notify(self, modifier=None):
        for observer in self._observers:
            if modifier != observer:
                observer.update(self)


# Example usage
class Data(Subject):
    def __init__(self, name=''):
        Subject.__init__(self)
        self.name = name
        self.data = 0

    def setData(self, data):
        self.data = data
        self.notify()

    def getData(self):
        return self.data


class HexViewer:
    def update(self, subject):
        print 'HexViewer: Subject %s has data 0x%x' % (subject.name, subject.getData())


class DecimalViewer:
    def update(self, subject):
        print 'DecimalViewer: Subject %s has data %d' % (subject.name, subject.getData())


# Example usage...
def main():
    data1 = Data('Data 1')
    data2 = Data('Data 2')
    view1 = DecimalViewer()
    view2 = HexViewer()
    data1.attach(view1)
    data1.attach(view2)
    data2.attach(view2)
    data2.attach(view1)

    print "Setting Data 1 = 10"
    data1.setData(10)
    print "Setting Data 2 = 15"
    data2.setData(15)
    print "Setting Data 1 = 3"
    data1.setData(3)
    print "Setting Data 2 = 5"
    data2.setData(5)
    print "Detach HexViewer from data1 and data2."
    data1.detach(view2)
    data2.detach(view2)
    print "Setting Data 1 = 10"
    data1.setData(10)
    print "Setting Data 2 = 15"
    data2.setData(15)

if __name__ == '__main__':
    main()   

The classes shown are meant for subclassing. The subject class is a bit modified in relation to the pattern desribed by Gamma et. al. When the notify method is called it also takes a modifier argument. This is convenient if you don't want an observer which has modified the subject to be updated again.

1 comment

Mauro B. Bianc 8 years, 8 months ago  # | flag

Excellent idea and solution. There is a way to make things even simpler and avoid having to call Notify & SetData (you can still call them if you prefer/need it). Python allows you to intercept the modification of attributes. :)

class Subject:
    def __init__(self):
        self._observers = []

    def __setattr__(self,attrname, value):
        # set the attribute
        self.__dict__[attrname] = value
        # and now notify observers
        self.notify()

    def attach(self, observer):
        if not observer in self._observers:
            self._observers.append(observer)

    def detach(self, observer):
        try:
            self._observers.remove(observer)
        except ValueError:
            pass

    def notify(self, modifier=None):
        for observer in self._observers:
            if modifier != observer:
                observer.update(self)


# Example usage
class Data(Subject):
    def __init__(self, name=''):
        Subject.__init__(self)
        self.name = name
        self.data = 0

    def setData(self, data):
        self.data = data
        # not needed anymore: self.notify()

    def getData(self):
        return self.data


class HexViewer:
    def update(self, subject):
        print 'HexViewer: Subject %s has data 0x%x' % (subject.name, subject.getData())


class DecimalViewer:
    def update(self, subject):
        print 'DecimalViewer: Subject %s has data %d' % (subject.name, subject.getData())


# Example usage...
def main():
    data1 = Data('Data 1')
    data2 = Data('Data 2')
    view1 = DecimalViewer()
    view2 = HexViewer()
    data1.attach(view1)
    data1.attach(view2)
    data2.attach(view2)
    data2.attach(view1)

# now try it like this:
    print "Setting Data 1 = 333"
    data1.data = 333;
    print "Setting Data 2 = 444"
    data2.data = 444;

#    print "Setting Data 1 = 10"
#    data1.setData(10)
#    print "Setting Data 2 = 15"
#    data2.setData(15)
#    print "Setting Data 1 = 3"
#    data1.setData(3)
#    print "Setting Data 2 = 5"
#    data2.setData(5)
#    print "Detach HexViewer from data1 and data2."
#    data1.detach(view2)
#    data2.detach(view2)
#    print "Setting Data 1 = 10"
#    data1.setData(10)
#    print "Setting Data 2 = 15"
#    data2.setData(15)

if __name__ == '__main__':
    main()
Created by Ilya Pashchenko on Mon, 4 Feb 2013 (PSF)
Python recipes (4591)
Ilya Pashchenko's recipes (2)

Required Modules

  • (none specified)

Other Information and Tasks