mescalinum mescalinum - 14 days ago 4
Python Question

how to properly implement Observer in python when the observer is [should be] destroyed

I'm implementing an observer-observable pattern in python:

This is the Observable class:

class Observable(object):
def __init__(self, value):
self.value = value
self.observers = []

def set(self, value):
old = self.value
self.value = value
self.notifyObservers(old, self.value)

def get(self):
return self.value

def addObserver(self, o):
self.observers.append(o)

def removeObserver(self, o):
if o in self.observers:
self.observers.remove(o)

def notifyObservers(self, old, new):
for o in self.observers:
o.valueChanged(old, new)


and this is an observer:

class Observer(object):
def __init__(self, foo):
self.foo = foo
self.foo.addObserver(self)

def __del__(self):
print('Observer.__del__ called')
self.foo.removeObserver(self)

def valueChanged(self, old, new):
print('foo changed from %s to %s' % (old, new))


The code works as expected.

But I need the
Observer
to be destroyed (i.e. when it goes unreferenced, it should remove itself from the list of observers in the
Observable
object).

The problem is that with this code,
Observer.__del__
never gets called if the
Observer
is in the list of observers of some
Observable
object.

Note that I don't necessarily destroy the
Observer
explicitly, it will also go unreferenced because of variable assignment, thus calling
removeObserver()
explicitly prior to destruction is not viable.

If I comment out
self.foo.addObserver(self)
, then there are no additional references to
Observer
, and calling
del
on it will call
Observer.__del__
.

The testcase for this scenario is:

foo = Observable(23)
bar = Observer(foo)
foo.set(44)
bar = None
foo.set(1)


it has two outcomes:


  • if
    self.foo.addObserver(self)
    is not commented out, it prints
    foo changed from 23 to 44
    and
    foo changed from 44 to 1

  • if
    self.foo.addObserver(self)
    is commented out, it prints
    Observer.__del__ called


Answer

It seems that weak reference will solve your problem. You change the observers to manage weak-references, e.g, by replacing the list in a weakref.WeakKeyDictionary, or by implementing some other weak-reference container. BTW, using a hashed type, such as a dictionary, will also be better than a list since removing an observer will be much more efficient.