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.

5 comments

Daniel T. 21 years, 6 months ago  # | flag

Tightening up... I would humbly suggest removing the Observer class, as it adds no value to the program. Also modifying the attach command to help avoid double additions would be a good thing:

class Subject:
    def attach(self, observer):
        if not observer in self._observers:
            self._observers.append(observer)
Jørgen Cederberg (author) 21 years, 3 months ago  # | flag

Removed observer class. Based on the comment, I removed the Observer Class. Additionally I changed the try-except clause, to except ValueError instead of all errors.

W.J. van der Laan 20 years, 5 months ago  # | flag

I think it was wrong to remove the Observer class. Observer was a kind of interface that documented the behaviour of an observer. Of course, in practice it isn't needed _because this is python and it is a dynamic language_, but it makes it clearer to the reader/maintainer.

Chris Grebeldinger 19 years ago  # | flag

subclassing necessary? I disagree, forcing observers to be subclassed from an IObserver class is unecessary. As long as your Subject class is well documented, there shouldn't be any confusion. If you really can't trust the observers, why not add extra checks to the attach method to enforce the contract.

class Subject:
    def attach(self, observer):
        # must have update method
        assert hasattr(observer,'update')
        # update must accept at least one argument
        assert observer.update.func_code.co_argcount > 0
        if not observer in self._observers:
            self._observers.append(observer)

Those checks don't cover every bad observer, but i'm sure it's possible with some extra work.

woobiboy 12 years, 6 months ago  # | flag

Here's an update, 9 years later, to take advantage of some newer language features added to python since you wrote your post.

class Subject(object):
  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=''):
    super(Data, self).__init__()
    self.name = name
    self._data = 0

  @property
  def data(self):
    return self._data

  @data.setter
  def data(self, value):
    self._data = value
    self.notify()



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


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


# 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.data = 10
  print "Setting Data 2 = 15"
  data2.data = 15
  print "Setting Data 1 = 3"
  data1.data = 3
  print "Setting Data 2 = 5"
  data2.data = 5
  print "Detach HexViewer from data1 and data2."
  data1.detach(view2)
  data2.detach(view2)
  print "Setting Data 1 = 10"
  data1.data = 10
  print "Setting Data 2 = 15"
  data2.data = 15

if __name__ == '__main__':
  main()
Created by Jørgen Cederberg on Wed, 5 Jun 2002 (PSF)
Python recipes (4591)
Jørgen Cederberg's recipes (2)

Required Modules

  • (none specified)

Other Information and Tasks