Puzzling OO design problem

George Sakkis gsakkis at rutgers.edu
Sat Apr 9 14:22:06 EDT 2005


> On second thought, I had doubts. Consider the following scenario:
>
>    A2 | f   <-   A3
>    ^             ^
>    |             |
>    B2 | f   <-   B3
>    ^             ^
>    |             |
>    C2       <-   C3 | g
>
> Assume g calls f. Since f is defined in B version 2, it should taken
> over unchanged into B version 3, and that should be the version g
> calls. However, with multiple inheritance doing depth-first search,
> giving the "upper" class priority over the "left", the first path
> searched is B3 - A3 - A2, and hence it finds f at A2. A quick test
> confirms this for old-style classes, which failed in exactly that
> way. But with new-style classes, it works, and finds f at B2 instead.
>
> So I dug through the documentation and found that new-style classes
> compute a monotonic linearization of the inheritance graph, observing
> local precedence order, using the algorithm also used in Dylan
> described here:
>
> http://www.webcom.com/haahr/dylan/linearization-oopsla96.html

That's right; you can actually access this linearization for new-style
classes by the '__mro__' class atrribute. See my example in the main
subthread of this thread that uses __mro__ to illustrate the need for
the dummy intermediate classes.

> And this algorithm will even work correctly if one leaves out B3
> in the example above, inheriting directly as in C3(A3,C2), because
> the ordering constraint that A2 comes before B2 will still be
> valid in the linearization for C3.
>
> However, in the following case (shortening the notation even more)
> it will fail, if a method defined at C3 is looked up at D4:
>
>    A1 - A2 - A3 - A4 - ...
>    |    |    |    |
>    B1 - B2 - +  - B4 - ...
>    |    |    |    |
>    C1 - +  - C3 - +  - ...
>    |    |    |    |
>    D1 - D2 - +  - D4 - ...
>    |    |    |    |
>
> The solution is simply to include C3 in the list of parents of D4, as
> in D4(C3,B4,D2). So for every hole in a column, you have to include
> the first class (or classes, if the hole spans multiple rows) to the
> left of the hole as parents if the class just below the hole, in
order
> from bottom to top. This adds the missing constraints, and should
> solve the problem without any need to write __metaclass__ stuff.

Nice. I had taken for granted that you need to fill in the holes
(D3,B3,C2), either manually or automa[tg]ically, but if you allow a
class to inherit from more than two bases, you can pick a set of
parents that does the job, without any boilerplate code or
__metaclass__ magic. The downside of this approach is that it's even
harder to see the big picture, as in the schematic notation above;
remember that each column is a different version that resides in a
separate module, so it's not obvious which classes should be the
parents of each variation. Another drawback in the general case is ease
of maintenance: if a new hole appears in the future or an old hole is
filled, you have to go back and change the parents of the affected
classes. In my case this is not an issue though; old versions are
'frozen', so the only expected change in the lattice is the addition of
new columns (versions).

> Interesting question. I learned a lot while thinking about that.
>
> - Dirk

I learned too, and I'm glad for this learning side-effect :-) Thanks !

George




More information about the Python-list mailing list