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

2

7

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
Bwana answered 22/11, 2016 at 11:41 Comment(1)
Have you looked at weakrefs? Weak references are designed to solve exactly this problem and have been a feature of Python since 2.4.Overzealous
O
5

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.

Ocko answered 22/11, 2016 at 11:52 Comment(0)
B
1

Solution: (changed Observable.observers to weakref.WeakKeyDictionary)

class Observable(object):
    def __init__(self, value):
        self.value = value
        self.observers = weakref.WeakKeyDictionary()

    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[o] = 1

    def removeObserver(self, o):
        del self.observers[o]

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

Also, it is not required to call .removeObserver(self) in the observer's destructor.

Bwana answered 22/11, 2016 at 21:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.