[Python-Dev] Oberver Pattern

Guido van Rossum guido@python.org
Fri, 10 May 2002 18:16:59 -0400

[Raymond Hettinger]
> 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).

[and later]
> 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);

I think these two quoted sections contadict each other.  If you have
to insert a call to PyNotify(self) everywhere the dict is modified,
you end up having to change a lot of places: every API that can change
the dict.

Note that you don't want to put the PyNotify() call in the
lowest-level insertdict() call, because then operations like update()
would notify the observer for each updated element, rather than once
for all updated elements.

It's worse for lists: there's a macro PyList_SET_ITEM().  You can't
change the macro to automatically call PyNotify(self), because (again)
that would notify the observer at too fine a granularity.  But that
means you have to update all code that *uses* PyList_SET_ITEM().  And
that's a lot.

> When not being observed, the performance penalty is nearly zero.

Again, I think you are being just a dat naive here -- all those jumps
(that are nearly always taken!) add up, and you're proposing to add
this to every mutable object.

> 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.

If you *have* to do this, I would implement (at the C level) just a
single observer object.  If multiple observers are needed, this can
easily be done using an adapter in Python.

--Guido van Rossum (home page: http://www.python.org/~guido/)