[Python-Dev] Oberver Pattern

Raymond Hettinger python@rcn.com
Fri, 10 May 2002 16:29:07 -0400


I'm thinking that this can be implemented in a way that has an almost
zero speed penalty unless observation is actually taking place for
an object and, even then, having only the cost of a function call.

Changed to existing code are expected to be minimal.
For a simple container type, implementation could be as small as
changing the flag line and inserting one line, PyNotify(self).

The addition of the int being_observed field would take place
implicitly through PyObject_HEAD.

From: "Steve Holden" <sholden@holdenweb.com>
> The idea, presumably, being to make the observer protocol implicit at the
C
> level?

Implemented at the C level and accessible at the Python level.

> I'd only thought of doing this explicitly in Python. It seems like
> this testing could have a huge performance penalty.

Taking dictionaryobject for example, anywhere the code assigns
a value to dp->me_key or dp->me_value, add a macro line:

  PyNotify(self);

which expands to:

  if (self->being_observed) PyObject_Notify_All(self);

When not being observed, the performance penalty is nearly zero.
When being observed, the cost is only a for loop over the function
calls:

for( i=0 ; i<numobservers ; i++)
    observerlist[i](self);

The cost is only setting up the loop and making the call.
The observer function itself may do as little as setting
an invalidate cache flag or do something timeconsuming.

>Alex's smart
> shelved, being implemented in Python, would clearly need some way (yet
more
> magic methods?) to hook into this mechanism.

No magic methods required, you can notify any callable:

tgt = ['the', 'quick' , 'brown']
m = MyClass()
Observer.attach(tgt, m.invalidatecache)
tgt[2] = 'red'   # updates tgt and triggers call: m.invalidatecache(tgt)


From: "Patrick K. O'Brien" <pobrien@orbtech.com>
> There is something about the Observer pattern that I've never fully liked.
> It seems to introduce a level of coupling that, to my way of thinking,
> "corrupts" the design of the object being observed.

There is no coupling.  The observed never knows anything about
the observers.  When a state change is made, it runs PyNotify(self).
No outside knowledge is required.  The GoF book specifically
lists a need for loose coupling as a force leading to the Observer Pattern.

> In real life, things can
> be observed without their knowledge.

When moving to a new house, you send out a change of address to
all of your observers.  When the phone number changes, you put on
a message with the new number.  This keeps the observers from
having to constantly check-up on you.

> So why should an observable object have
> to keep track of all of its listeners and notify them regularly?

A standard block of registry code will do the work.  The only responsibility
of the observed is to say, "hey, i've changed".

> But I'm willing to hear about the benefits of your idea. A standardized
> implementation might overcome my initial reluctance. So what are the
> benefits of formalizing this?

Thanks.  Alex's code is a prime example.  Not knowing which shelve values
have changed, it is forced to do a 100% rewrite of all data in the shelve
whether or not any changes have occurred.  What a bummer.

From: "holger krekel" <pyth@devel.trillke.net>
> doesn't "Any code affecting object state" include an awful lot of places?

Immutables don't need to be observable.  So that leaves jcontainers
like dictionaries and lists.  The code for these objects each have about a
dozen places with some action like, ob_item[x] = val.  Outside the object's
code, the direct accesses have been factored-out into just a couple of
of direct access macros in abstract.h.

If we want to exclude some object class from participating, we just leave
it alone.  The players identify themselves with:

   tp_flags |= Py_TPFLAGS_OBSERVABLE



Raymond Hettinger