Observer — Python 3 Patterns, Recipes and Idioms (2024)

Decoupling code behavior

Observer, and a category of callbacks called “multiple dispatching (not inDesign Patterns)” including the Visitor from Design Patterns. Like theother forms of callback, this contains a hook point where you can change code.The difference is in the observer’s completely dynamic nature. It is often usedfor the specific case of changes based on other object’s change of state, but isalso the basis of event management. Anytime you want to decouple the source ofthe call from the called code in a completely dynamic way.

The observer pattern solves a fairly common problem: What if a group of objectsneeds to update themselves when some object changes state? This can be seen inthe “model-view” aspect of Smalltalk’s MVC (model-view-controller), or thealmost-equivalent “Document-View Architecture.” Suppose that you have some data(the “document”) and more than one view, say a plot and a textual view. When youchange the data, the two views must know to update themselves, and that’s whatthe observer facilitates. It’s a common enough problem that its solution hasbeen made a part of the standard java.util library.

There are two types of objects used to implement the observer pattern in Python.The Observable class keeps track of everybody who wants to be informed whena change happens, whether the “state” has changed or not. When someone says “OK,everybody should check and potentially update themselves,” the Observableclass performs this task by calling the notifyObservers( ) method for eachone on the list. The notifyObservers( ) method is part of the base classObservable.

There are actually two “things that change” in the observer pattern: thequantity of observing objects and the way an update occurs. That is, theobserver pattern allows you to modify both of these without affecting thesurrounding code.

Observer is an “interface” class that only has one member function,update( ). This function is called by the object that’s being observed, whenthat object decides its time to update all its observers. The arguments areoptional; you could have an update( ) with no arguments and that would stillfit the observer pattern; however this is more general-it allows the observedobject to pass the object that caused the update (since an Observer may beregistered with more than one observed object) and any extra information ifthat’s helpful, rather than forcing the Observer object to hunt around tosee who is updating and to fetch any other information it needs.

The “observed object” that decides when and how to do the updating will becalled the Observable.

Observable has a flag to indicate whether it’s been changed. In a simplerdesign, there would be no flag; if something happened, everyone would benotified. The flag allows you to wait, and only notify the Observers whenyou decide the time is right. Notice, however, that the control of the flag’sstate is protected, so that only an inheritor can decide what constitutes achange, and not the end user of the resulting derived Observer class.

Most of the work is done in notifyObservers( ). If the changed flag hasnot been set, this does nothing. Otherwise, it first clears the changed flagso repeated calls to notifyObservers( ) won’t waste time. This is donebefore notifying the observers in case the calls to update( ) do anythingthat causes a change back to this Observable object. Then it moves throughthe set and calls back to the update( ) member function of eachObserver.

At first it may appear that you can use an ordinary Observable object tomanage the updates. But this doesn’t work; to get an effect, you must inheritfrom Observable and somewhere in your derived-class code call setChanged(). This is the member function that sets the “changed” flag, which means thatwhen you call notifyObservers( ) all of the observers will, in fact, getnotified. Where you call setChanged( ) depends on the logic of yourprogram.

Observing Flowers

Since Python doesn’t have standard library components to support the observerpattern (like Java does), we must first create one. The simplest thing to do istranslate the Java standard library Observer and Observable classes.This also provides easier translation from Java code that uses these libraries.

In trying to do this, we encounter a minor snag, which is the fact that Java hasa synchronized keyword that provides built-in support for threadsynchronization. We could certainly accomplish the same thing by hand, usingcode like this:

# Util/ToSynch.pyimport threadingclass ToSynch: def __init__(self): self.mutex = threading.RLock() self.val = 1 def aSynchronizedMethod(self): self.mutex.acquire() try: self.val += 1 return self.val finally: self.mutex.release()

But this rapidly becomes tedious to write and to read. Peter Norvig provided mewith a much nicer solution:

# Util/Synchronization.py'''Simple emulation of Java's 'synchronized'keyword, from Peter Norvig.'''import threadingdef synchronized(method): def f(*args): self = args[0] self.mutex.acquire(); # print(method.__name__, 'acquired') try: return apply(method, args) finally: self.mutex.release(); # print(method.__name__, 'released') return fdef synchronize(klass, names=None): """Synchronize methods in the given class. Only synchronize the methods whose names are given, or all methods if names=None.""" if type(names)==type(''): names = names.split() for (name, val) in klass.__dict__.items(): if callable(val) and name != '__init__' and \ (names == None or name in names): # print("synchronizing", name) klass.__dict__[name] = synchronized(val)# You can create your own self.mutex, or inherit# from this class:class Synchronization: def __init__(self): self.mutex = threading.RLock()

The synchronized( ) function takes a method and wraps it in a function thatadds the mutex functionality. The method is called inside this function:

return apply(method, args)

and as the return statement passes through the finally clause, the mutexis released.

This is in some ways the Decorator design pattern, but much simpler to createand use. All you have to say is:

myMethod = synchronized(myMethod)

To surround your method with a mutex.

synchronize( ) is a convenience function that applies synchronized( ) toan entire class, either all the methods in the class (the default) or selectedmethods which are named in a string as the second argument.

Finally, for synchronized( ) to work there must be a self.mutex createdin every class that uses synchronized( ). This can be created by hand by theclass author, but it’s more consistent to use inheritance, so the base classSynchronization is provided.

Here’s a simple test of the Synchronization module:

# Util/TestSynchronization.pyfrom Synchronization import *# To use for a method:class C(Synchronization): def __init__(self): Synchronization.__init__(self) self.data = 1 def m(self): self.data += 1 return self.data m = synchronized(m) def f(self): return 47 def g(self): return 'spam'# So m is synchronized, f and g are not.c = C()# On the class level:class D(C): def __init__(self): C.__init__(self) # You must override an un-synchronized method # in order to synchronize it (just like Java): def f(self): C.f(self)# Synchronize every (defined) method in the class:synchronize(D)d = D()d.f() # Synchronizedd.g() # Not synchronizedd.m() # Synchronized (in the base class)class E(C): def __init__(self): C.__init__(self) def m(self): C.m(self) def g(self): C.g(self) def f(self): C.f(self)# Only synchronizes m and g. Note that m ends up# being doubly-wrapped in synchronization, which# doesn't hurt anything but is inefficient:synchronize(E, 'm g')e = E()e.f()e.g()e.m()

You must call the base class constructor for Synchronization, but that’sall. In class C you can see the use of synchronized( ) for m,leaving f and g alone. Class D has all its methods synchronized enmasse, and class E uses the convenience function to synchronize m andg. Note that since m ends up being synchronized twice, it will beentered and left twice for every call, which isn’t very desirable [there may bea fix for this]:

# Util/Observer.py# Class support for "observer" pattern.from Synchronization import *class Observer: def update(observable, arg): '''Called when the observed object is modified. You call an Observable object's notifyObservers method to notify all the object's observers of the change.''' passclass Observable(Synchronization): def __init__(self): self.obs = [] self.changed = 0 Synchronization.__init__(self) def addObserver(self, observer): if observer not in self.obs: self.obs.append(observer) def deleteObserver(self, observer): self.obs.remove(observer) def notifyObservers(self, arg = None): '''If 'changed' indicates that this object has changed, notify all its observers, then call clearChanged(). Each observer has its update() called with two arguments: this observable object and the generic 'arg'.''' self.mutex.acquire() try: if not self.changed: return # Make a local copy in case of synchronous # additions of observers: localArray = self.obs[:] self.clearChanged() finally: self.mutex.release() # Updating is not required to be synchronized: for observer in localArray: observer.update(self, arg) def deleteObservers(self): self.obs = [] def setChanged(self): self.changed = 1 def clearChanged(self): self.changed = 0 def hasChanged(self): return self.changed def countObservers(self): return len(self.obs)synchronize(Observable, "addObserver deleteObserver deleteObservers " + "setChanged clearChanged hasChanged " + "countObservers")

Using this library, here is an example of the observer pattern:

# Observer/ObservedFlower.py# Demonstration of "observer" pattern.import syssys.path += ['../util']from Observer import Observer, Observableclass Flower: def __init__(self): self.isOpen = 0 self.openNotifier = Flower.OpenNotifier(self) self.closeNotifier= Flower.CloseNotifier(self) def open(self): # Opens its petals self.isOpen = 1 self.openNotifier.notifyObservers() self.closeNotifier.open() def close(self): # Closes its petals self.isOpen = 0 self.closeNotifier.notifyObservers() self.openNotifier.close() def closing(self): return self.closeNotifier class OpenNotifier(Observable): def __init__(self, outer): Observable.__init__(self) self.outer = outer self.alreadyOpen = 0 def notifyObservers(self): if self.outer.isOpen and \ not self.alreadyOpen: self.setChanged() Observable.notifyObservers(self) self.alreadyOpen = 1 def close(self): self.alreadyOpen = 0 class CloseNotifier(Observable): def __init__(self, outer): Observable.__init__(self) self.outer = outer self.alreadyClosed = 0 def notifyObservers(self): if not self.outer.isOpen and \ not self.alreadyClosed: self.setChanged() Observable.notifyObservers(self) self.alreadyClosed = 1 def open(self): alreadyClosed = 0class Bee: def __init__(self, name): self.name = name self.openObserver = Bee.OpenObserver(self) self.closeObserver = Bee.CloseObserver(self) # An inner class for observing openings: class OpenObserver(Observer): def __init__(self, outer): self.outer = outer def update(self, observable, arg): print("Bee " + self.outer.name + \) "'s breakfast time!" # Another inner class for closings: class CloseObserver(Observer): def __init__(self, outer): self.outer = outer def update(self, observable, arg): print("Bee " + self.outer.name + \) "'s bed time!"class Hummingbird: def __init__(self, name): self.name = name self.openObserver = \ Hummingbird.OpenObserver(self) self.closeObserver = \ Hummingbird.CloseObserver(self) class OpenObserver(Observer): def __init__(self, outer): self.outer = outer def update(self, observable, arg): print("Hummingbird " + self.outer.name + \ "'s breakfast time!") class CloseObserver(Observer): def __init__(self, outer): self.outer = outer def update(self, observable, arg): print("Hummingbird " + self.outer.name + \ "'s bed time!")f = Flower()ba = Bee("Eric")bb = Bee("Eric 0.5")ha = Hummingbird("A")hb = Hummingbird("B")f.openNotifier.addObserver(ha.openObserver)f.openNotifier.addObserver(hb.openObserver)f.openNotifier.addObserver(ba.openObserver)f.openNotifier.addObserver(bb.openObserver)f.closeNotifier.addObserver(ha.closeObserver)f.closeNotifier.addObserver(hb.closeObserver)f.closeNotifier.addObserver(ba.closeObserver)f.closeNotifier.addObserver(bb.closeObserver)# Hummingbird 2 decides to sleep in:f.openNotifier.deleteObserver(hb.openObserver)# A change that interests observers:f.open()f.open() # It's already open, no change.# Bee 1 doesn't want to go to bed:f.closeNotifier.deleteObserver(ba.closeObserver)f.close()f.close() # It's already closed; no changef.openNotifier.deleteObservers()f.open()f.close()

The events of interest are that a Flower can open or close. Because of theuse of the inner class idiom, both these events can be separately observablephenomena. OpenNotifier and CloseNotifier both inherit Observable,so they have access to setChanged( ) and can be handed to anything thatneeds an Observable.

The inner class idiom also comes in handy to define more than one kind ofObserver, in Bee and Hummingbird, since both those classes may wantto independently observe Flower openings and closings. Notice how the innerclass idiom provides something that has most of the benefits of inheritance (theability to access the private data in the outer class, for example) withoutthe same restrictions.

In main( ), you can see one of the prime benefits of the observer pattern:the ability to change behavior at run time by dynamically registering and un-registering Observers with Observables.

If you study the code above you’ll see that OpenNotifier andCloseNotifier use the basic Observable interface. This means that youcould inherit other completely different Observer classes; the onlyconnection the Observers have with Flowers is the Observerinterface.

A Visual Example of Observers

The following example is similar to the ColorBoxes example from Thinking inJava. Boxes are placed in a grid on the screen and each one is initialized to arandom color. In addition, each box implements the Observer interfaceand is registered with an Observable object. When you click on a box, all ofthe other boxes are notified that a change has been made because theObservable object automatically calls each Observer object’s update() method. Inside this method, the box checks to see if it’s adjacent to theone that was clicked, and if so it changes its color to match the clicked box.(NOTE: this example has not been converted. See further down for a version thathas the GUI but not the Observers, in PythonCard.):

# Observer/BoxObserver.py# Demonstration of Observer pattern using# Java's built-in observer classes.# You must inherit a type of Observable:class BoxObservable(Observable): def notifyObservers(self, Object b): # Otherwise it won't propagate changes: setChanged() super.notifyObservers(b)class BoxObserver(JFrame): Observable notifier = BoxObservable() def __init__(self, grid): setTitle("Demonstrates Observer pattern") Container cp = getContentPane() cp.setLayout(GridLayout(grid, grid)) for(int x = 0 x < grid x++) for(int y = 0 y < grid y++) cp.add(OCBox(x, y, notifier)) def main(self, String[] args): grid = 8 if(args.length > 0) grid = Integer.parseInt(args[0]) JFrame f = BoxObserver(grid) f.setSize(500, 400) f.setVisible(1) # JDK 1.3: f.setDefaultCloseOperation(EXIT_ON_CLOSE) # Add a WindowAdapter if you have JDK 1.2class OCBox(JPanel) implements Observer: Color cColor = newColor() colors = [ Color.black, Color.blue, Color.cyan, Color.darkGray, Color.gray, Color.green, Color.lightGray, Color.magenta, Color.orange, Color.pink, Color.red, Color.white, Color.yellow ] def newColor(): return colors[ (int)(Math.random() * colors.length) ] def __init__(self, x, y, Observable notifier): self.x = x self.y = y notifier.addObserver(self) self.notifier = notifier addMouseListener(ML()) def paintComponent(self, Graphics g): super.paintComponent(g) g.setColor(cColor) Dimension s = getSize() g.fillRect(0, 0, s.width, s.height) class ML(MouseAdapter): def mousePressed(self, MouseEvent e): notifier.notifyObservers(OCBox.self) def update(self, Observable o, Object arg): OCBox clicked = (OCBox)arg if(nextTo(clicked)): cColor = clicked.cColor repaint() def nextTo(OCBox b): return Math.abs(x - b.x) <= 1 && Math.abs(y - b.y) <= 1

When you first look at the online documentation for Observable, it’s a bitconfusing because it appears that you can use an ordinary Observable objectto manage the updates. But this doesn’t work; try it-inside BoxObserver,create an Observable object instead of a BoxObservable object and seewhat happens: nothing. To get an effect, you must inherit from Observableand somewhere in your derived-class code call setChanged( ). This is themethod that sets the “changed” flag, which means that when you callnotifyObservers( ) all of the observers will, in fact, get notified. In theexample above setChanged( ) is simply called within notifyObservers( ),but you could use any criterion you want to decide when to call setChanged().

BoxObserver contains a single Observable object called notifier, andevery time an OCBox object is created, it is tied to notifier. InOCBox, whenever you click the mouse the notifyObservers( ) method iscalled, passing the clicked object in as an argument so that all the boxesreceiving the message (in their update( ) method) know who was clicked andcan decide whether to change themselves or not. Using a combination of code innotifyObservers( ) and update( ) you can work out some fairly complexschemes.

It might appear that the way the observers are notified must be frozen atcompile time in the notifyObservers( ) method. However, if you look moreclosely at the code above you’ll see that the only place in BoxObserver orOCBox where you’re aware that you’re working with a BoxObservable is atthe point of creation of the Observable object-from then on everything usesthe basic Observable interface. This means that you could inherit otherObservable classes and swap them at run time if you want to changenotification behavior then.

Here is a version of the above that doesn’t use the Observer pattern, written byKevin Altis using PythonCard, and placed here as a starting point for atranslation that does include Observer:

# Observer/BoxObserverPythonCard.py""" Written by Kevin Altis as a first-cut forconverting BoxObserver to Python. The Observerhasn't been integrated yet.To run this program, you must:Install WxPython fromhttp://www.wxpython.org/download.phpInstall PythonCard. See:http://pythoncard.sourceforge.net"""from PythonCardPrototype import log, modelimport randomGRID = 8class ColorBoxesTest(model.Background): def on_openBackground(self, event): self.document = [] for row in range(GRID): line = [] for column in range(GRID): line.append(self.createBox(row, column)) self.document.append(line[:]) def createBox(self, row, column): colors = ['black', 'blue', 'cyan', 'darkGray', 'gray', 'green', 'lightGray', 'magenta', 'orange', 'pink', 'red', 'white', 'yellow'] width, height = self.panel.GetSizeTuple() boxWidth = width / GRID boxHeight = height / GRID log.info("width:" + str(width) + " height:" + str(height)) log.info("boxWidth:" + str(boxWidth) + " boxHeight:" + str(boxHeight)) # use an empty image, though some other # widgets would work just as well boxDesc = {'type':'Image', 'size':(boxWidth, boxHeight), 'file':''} name = 'box-%d-%d' % (row, column) # There is probably a 1 off error in the # calculation below since the boxes should # probably have a slightly different offset # to prevent overlaps boxDesc['position'] = \ (column * boxWidth, row * boxHeight) boxDesc['name'] = name boxDesc['backgroundColor'] = \ random.choice(colors) self.components[name] = boxDesc return self.components[name] def changeNeighbors(self, row, column, color): # This algorithm will result in changing the # color of some boxes more than once, so an # OOP solution where only neighbors are asked # to change or boxes check to see if they are # neighbors before changing would be better # per the original example does the whole grid # need to change its state at once like in a # Life program? should the color change # in the propogation of another notification # event? for r in range(max(0, row - 1), min(GRID, row + 2)): for c in range(max(0, column - 1), min(GRID, column + 2)): self.document[r][c].backgroundColor=color # this is a background handler, so it isn't # specific to a single widget. Image widgets # don't have a mouseClick event (wxCommandEvent # in wxPython) def on_mouseUp(self, event): target = event.target prefix, row, column = target.name.split('-') self.changeNeighbors(int(row), int(column), target.backgroundColor)if __name__ == '__main__': app = model.PythonCardApp(ColorBoxesTest) app.MainLoop()

This is the resource file for running the program (see PythonCard for details):

# Observer/BoxObserver.rsrc.py{'stack':{'type':'Stack', 'name':'BoxObserver', 'backgrounds': [ { 'type':'Background', 'name':'bgBoxObserver', 'title':'Demonstrates Observer pattern', 'position':(5, 5), 'size':(500, 400), 'components': [] # end components} # end background] # end backgrounds} }

Exercises

  1. Using the approach in Synchronization.py, create a tool that willautomatically wrap all the methods in a class to provide an execution trace,so that you can see the name of the method and when it is entered andexited.
  2. Create a minimal Observer-Observable design in two classes. Just create thebare minimum in the two classes, then demonstrate your design by creatingone Observable and many Observers, and cause the Observable toupdate the Observers.
  3. Modify BoxObserver.py to turn it into a simple game. If any of thesquares surrounding the one you clicked is part of a contiguous patch of thesame color, then all the squares in that patch are changed to the color youclicked on. You can configure the game for competition between players or tokeep track of the number of clicks that a single player uses to turn thefield into a single color. You may also want to restrict a player’s color tothe first one that was chosen.
Observer — Python 3 Patterns, Recipes and Idioms (2024)
Top Articles
Latest Posts
Article information

Author: Duane Harber

Last Updated:

Views: 6833

Rating: 4 / 5 (71 voted)

Reviews: 94% of readers found this page helpful

Author information

Name: Duane Harber

Birthday: 1999-10-17

Address: Apt. 404 9899 Magnolia Roads, Port Royceville, ID 78186

Phone: +186911129794335

Job: Human Hospitality Planner

Hobby: Listening to music, Orienteering, Knapping, Dance, Mountain biking, Fishing, Pottery

Introduction: My name is Duane Harber, I am a modern, clever, handsome, fair, agreeable, inexpensive, beautiful person who loves writing and wants to share my knowledge and understanding with you.