2024-04-15 07:01:15 -03:00
|
|
|
.. _python_2.3_mro:
|
|
|
|
|
|
|
|
The Python 2.3 Method Resolution Order
|
|
|
|
======================================
|
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
This is a historical document, provided as an appendix to the official
|
|
|
|
documentation.
|
|
|
|
The Method Resolution Order discussed here was *introduced* in Python 2.3,
|
|
|
|
but it is still used in later versions -- including Python 3.
|
|
|
|
|
|
|
|
By `Michele Simionato <https://www.phyast.pitt.edu/~micheles/>`__.
|
|
|
|
|
|
|
|
:Abstract:
|
|
|
|
|
|
|
|
*This document is intended for Python programmers who want to
|
|
|
|
understand the C3 Method Resolution Order used in Python 2.3.
|
|
|
|
Although it is not intended for newbies, it is quite pedagogical with
|
|
|
|
many worked out examples. I am not aware of other publicly available
|
|
|
|
documents with the same scope, therefore it should be useful.*
|
|
|
|
|
|
|
|
Disclaimer:
|
|
|
|
|
|
|
|
*I donate this document to the Python Software Foundation, under the
|
|
|
|
Python 2.3 license. As usual in these circumstances, I warn the
|
|
|
|
reader that what follows* should *be correct, but I don't give any
|
|
|
|
warranty. Use it at your own risk and peril!*
|
|
|
|
|
|
|
|
Acknowledgments:
|
|
|
|
|
|
|
|
*All the people of the Python mailing list who sent me their support.
|
|
|
|
Paul Foley who pointed out various imprecisions and made me to add the
|
|
|
|
part on local precedence ordering. David Goodger for help with the
|
|
|
|
formatting in reStructuredText. David Mertz for help with the editing.
|
|
|
|
Finally, Guido van Rossum who enthusiastically added this document to
|
|
|
|
the official Python 2.3 home-page.*
|
|
|
|
|
|
|
|
The beginning
|
|
|
|
-------------
|
|
|
|
|
|
|
|
*Felix qui potuit rerum cognoscere causas* -- Virgilius
|
|
|
|
|
|
|
|
Everything started with a post by Samuele Pedroni to the Python
|
|
|
|
development mailing list [#]_. In his post, Samuele showed that the
|
|
|
|
Python 2.2 method resolution order is not monotonic and he proposed to
|
|
|
|
replace it with the C3 method resolution order. Guido agreed with his
|
|
|
|
arguments and therefore now Python 2.3 uses C3. The C3 method itself
|
|
|
|
has nothing to do with Python, since it was invented by people working
|
|
|
|
on Dylan and it is described in a paper intended for lispers [#]_. The
|
|
|
|
present paper gives a (hopefully) readable discussion of the C3
|
|
|
|
algorithm for Pythonistas who want to understand the reasons for the
|
|
|
|
change.
|
|
|
|
|
|
|
|
First of all, let me point out that what I am going to say only applies
|
|
|
|
to the *new style classes* introduced in Python 2.2: *classic classes*
|
|
|
|
maintain their old method resolution order, depth first and then left to
|
|
|
|
right. Therefore, there is no breaking of old code for classic classes;
|
|
|
|
and even if in principle there could be breaking of code for Python 2.2
|
|
|
|
new style classes, in practice the cases in which the C3 resolution
|
|
|
|
order differs from the Python 2.2 method resolution order are so rare
|
|
|
|
that no real breaking of code is expected. Therefore:
|
|
|
|
|
|
|
|
*Don't be scared!*
|
|
|
|
|
|
|
|
Moreover, unless you make strong use of multiple inheritance and you
|
|
|
|
have non-trivial hierarchies, you don't need to understand the C3
|
|
|
|
algorithm, and you can easily skip this paper. On the other hand, if
|
|
|
|
you really want to know how multiple inheritance works, then this paper
|
|
|
|
is for you. The good news is that things are not as complicated as you
|
|
|
|
might expect.
|
|
|
|
|
|
|
|
Let me begin with some basic definitions.
|
|
|
|
|
|
|
|
1) Given a class C in a complicated multiple inheritance hierarchy, it
|
|
|
|
is a non-trivial task to specify the order in which methods are
|
|
|
|
overridden, i.e. to specify the order of the ancestors of C.
|
|
|
|
|
|
|
|
2) The list of the ancestors of a class C, including the class itself,
|
|
|
|
ordered from the nearest ancestor to the furthest, is called the
|
|
|
|
class precedence list or the *linearization* of C.
|
|
|
|
|
|
|
|
3) The *Method Resolution Order* (MRO) is the set of rules that
|
|
|
|
construct the linearization. In the Python literature, the idiom
|
|
|
|
"the MRO of C" is also used as a synonymous for the linearization of
|
|
|
|
the class C.
|
|
|
|
|
|
|
|
4) For instance, in the case of single inheritance hierarchy, if C is a
|
|
|
|
subclass of C1, and C1 is a subclass of C2, then the linearization of
|
|
|
|
C is simply the list [C, C1 , C2]. However, with multiple
|
|
|
|
inheritance hierarchies, the construction of the linearization is
|
|
|
|
more cumbersome, since it is more difficult to construct a
|
|
|
|
linearization that respects *local precedence ordering* and
|
|
|
|
*monotonicity*.
|
|
|
|
|
|
|
|
5) I will discuss the local precedence ordering later, but I can give
|
|
|
|
the definition of monotonicity here. A MRO is monotonic when the
|
|
|
|
following is true: *if C1 precedes C2 in the linearization of C,
|
|
|
|
then C1 precedes C2 in the linearization of any subclass of C*.
|
|
|
|
Otherwise, the innocuous operation of deriving a new class could
|
|
|
|
change the resolution order of methods, potentially introducing very
|
|
|
|
subtle bugs. Examples where this happens will be shown later.
|
|
|
|
|
|
|
|
6) Not all classes admit a linearization. There are cases, in
|
|
|
|
complicated hierarchies, where it is not possible to derive a class
|
|
|
|
such that its linearization respects all the desired properties.
|
|
|
|
|
|
|
|
Here I give an example of this situation. Consider the hierarchy
|
|
|
|
|
|
|
|
>>> O = object
|
|
|
|
>>> class X(O): pass
|
|
|
|
>>> class Y(O): pass
|
|
|
|
>>> class A(X,Y): pass
|
|
|
|
>>> class B(Y,X): pass
|
|
|
|
|
|
|
|
which can be represented with the following inheritance graph, where I
|
|
|
|
have denoted with O the ``object`` class, which is the beginning of any
|
|
|
|
hierarchy for new style classes:
|
|
|
|
|
|
|
|
.. code-block:: text
|
|
|
|
|
|
|
|
-----------
|
|
|
|
| |
|
|
|
|
| O |
|
|
|
|
| / \ |
|
|
|
|
- X Y /
|
|
|
|
| / | /
|
|
|
|
| / |/
|
|
|
|
A B
|
|
|
|
\ /
|
|
|
|
?
|
|
|
|
|
|
|
|
In this case, it is not possible to derive a new class C from A and B,
|
|
|
|
since X precedes Y in A, but Y precedes X in B, therefore the method
|
|
|
|
resolution order would be ambiguous in C.
|
|
|
|
|
|
|
|
Python 2.3 raises an exception in this situation (TypeError: MRO
|
|
|
|
conflict among bases Y, X) forbidding the naive programmer from creating
|
|
|
|
ambiguous hierarchies. Python 2.2 instead does not raise an exception,
|
|
|
|
but chooses an *ad hoc* ordering (CABXYO in this case).
|
|
|
|
|
|
|
|
The C3 Method Resolution Order
|
|
|
|
------------------------------
|
|
|
|
|
|
|
|
Let me introduce a few simple notations which will be useful for the
|
|
|
|
following discussion. I will use the shortcut notation::
|
|
|
|
|
|
|
|
C1 C2 ... CN
|
|
|
|
|
|
|
|
to indicate the list of classes [C1, C2, ... , CN].
|
|
|
|
|
|
|
|
The *head* of the list is its first element::
|
|
|
|
|
|
|
|
head = C1
|
|
|
|
|
|
|
|
whereas the *tail* is the rest of the list::
|
|
|
|
|
|
|
|
tail = C2 ... CN.
|
|
|
|
|
|
|
|
I shall also use the notation::
|
|
|
|
|
|
|
|
C + (C1 C2 ... CN) = C C1 C2 ... CN
|
|
|
|
|
|
|
|
to denote the sum of the lists [C] + [C1, C2, ... ,CN].
|
|
|
|
|
|
|
|
Now I can explain how the MRO works in Python 2.3.
|
|
|
|
|
|
|
|
Consider a class C in a multiple inheritance hierarchy, with C
|
|
|
|
inheriting from the base classes B1, B2, ... , BN. We want to
|
|
|
|
compute the linearization L[C] of the class C. The rule is the
|
|
|
|
following:
|
|
|
|
|
|
|
|
*the linearization of C is the sum of C plus the merge of the
|
|
|
|
linearizations of the parents and the list of the parents.*
|
|
|
|
|
|
|
|
In symbolic notation::
|
|
|
|
|
|
|
|
L[C(B1 ... BN)] = C + merge(L[B1] ... L[BN], B1 ... BN)
|
|
|
|
|
|
|
|
In particular, if C is the ``object`` class, which has no parents, the
|
|
|
|
linearization is trivial::
|
|
|
|
|
|
|
|
L[object] = object.
|
|
|
|
|
|
|
|
However, in general one has to compute the merge according to the following
|
|
|
|
prescription:
|
|
|
|
|
|
|
|
*take the head of the first list, i.e L[B1][0]; if this head is not in
|
|
|
|
the tail of any of the other lists, then add it to the linearization
|
|
|
|
of C and remove it from the lists in the merge, otherwise look at the
|
|
|
|
head of the next list and take it, if it is a good head. Then repeat
|
|
|
|
the operation until all the class are removed or it is impossible to
|
|
|
|
find good heads. In this case, it is impossible to construct the
|
|
|
|
merge, Python 2.3 will refuse to create the class C and will raise an
|
|
|
|
exception.*
|
|
|
|
|
|
|
|
This prescription ensures that the merge operation *preserves* the
|
|
|
|
ordering, if the ordering can be preserved. On the other hand, if the
|
|
|
|
order cannot be preserved (as in the example of serious order
|
|
|
|
disagreement discussed above) then the merge cannot be computed.
|
|
|
|
|
|
|
|
The computation of the merge is trivial if C has only one parent
|
|
|
|
(single inheritance); in this case::
|
|
|
|
|
|
|
|
L[C(B)] = C + merge(L[B],B) = C + L[B]
|
|
|
|
|
|
|
|
However, in the case of multiple inheritance things are more cumbersome
|
|
|
|
and I don't expect you can understand the rule without a couple of
|
|
|
|
examples ;-)
|
|
|
|
|
|
|
|
Examples
|
|
|
|
--------
|
|
|
|
|
|
|
|
First example. Consider the following hierarchy:
|
|
|
|
|
|
|
|
>>> O = object
|
|
|
|
>>> class F(O): pass
|
|
|
|
>>> class E(O): pass
|
|
|
|
>>> class D(O): pass
|
|
|
|
>>> class C(D,F): pass
|
|
|
|
>>> class B(D,E): pass
|
|
|
|
>>> class A(B,C): pass
|
|
|
|
|
|
|
|
In this case the inheritance graph can be drawn as:
|
|
|
|
|
|
|
|
.. code-block:: text
|
|
|
|
|
|
|
|
6
|
|
|
|
---
|
|
|
|
Level 3 | O | (more general)
|
|
|
|
/ --- \
|
|
|
|
/ | \ |
|
|
|
|
/ | \ |
|
|
|
|
/ | \ |
|
|
|
|
--- --- --- |
|
|
|
|
Level 2 3 | D | 4| E | | F | 5 |
|
|
|
|
--- --- --- |
|
|
|
|
\ \ _ / | |
|
|
|
|
\ / \ _ | |
|
|
|
|
\ / \ | |
|
|
|
|
--- --- |
|
|
|
|
Level 1 1 | B | | C | 2 |
|
|
|
|
--- --- |
|
|
|
|
\ / |
|
|
|
|
\ / \ /
|
|
|
|
---
|
|
|
|
Level 0 0 | A | (more specialized)
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
The linearizations of O,D,E and F are trivial::
|
|
|
|
|
|
|
|
L[O] = O
|
|
|
|
L[D] = D O
|
|
|
|
L[E] = E O
|
|
|
|
L[F] = F O
|
|
|
|
|
|
|
|
The linearization of B can be computed as::
|
|
|
|
|
|
|
|
L[B] = B + merge(DO, EO, DE)
|
|
|
|
|
|
|
|
We see that D is a good head, therefore we take it and we are reduced to
|
|
|
|
compute ``merge(O,EO,E)``. Now O is not a good head, since it is in the
|
|
|
|
tail of the sequence EO. In this case the rule says that we have to
|
|
|
|
skip to the next sequence. Then we see that E is a good head; we take
|
|
|
|
it and we are reduced to compute ``merge(O,O)`` which gives O. Therefore::
|
|
|
|
|
|
|
|
L[B] = B D E O
|
|
|
|
|
|
|
|
Using the same procedure one finds::
|
|
|
|
|
|
|
|
L[C] = C + merge(DO,FO,DF)
|
|
|
|
= C + D + merge(O,FO,F)
|
|
|
|
= C + D + F + merge(O,O)
|
|
|
|
= C D F O
|
|
|
|
|
|
|
|
Now we can compute::
|
|
|
|
|
|
|
|
L[A] = A + merge(BDEO,CDFO,BC)
|
|
|
|
= A + B + merge(DEO,CDFO,C)
|
|
|
|
= A + B + C + merge(DEO,DFO)
|
|
|
|
= A + B + C + D + merge(EO,FO)
|
|
|
|
= A + B + C + D + E + merge(O,FO)
|
|
|
|
= A + B + C + D + E + F + merge(O,O)
|
|
|
|
= A B C D E F O
|
|
|
|
|
|
|
|
In this example, the linearization is ordered in a pretty nice way
|
|
|
|
according to the inheritance level, in the sense that lower levels (i.e.
|
|
|
|
more specialized classes) have higher precedence (see the inheritance
|
|
|
|
graph). However, this is not the general case.
|
|
|
|
|
|
|
|
I leave as an exercise for the reader to compute the linearization for
|
|
|
|
my second example:
|
|
|
|
|
|
|
|
>>> O = object
|
|
|
|
>>> class F(O): pass
|
|
|
|
>>> class E(O): pass
|
|
|
|
>>> class D(O): pass
|
|
|
|
>>> class C(D,F): pass
|
|
|
|
>>> class B(E,D): pass
|
|
|
|
>>> class A(B,C): pass
|
|
|
|
|
|
|
|
The only difference with the previous example is the change B(D,E) -->
|
|
|
|
B(E,D); however even such a little modification completely changes the
|
|
|
|
ordering of the hierarchy:
|
|
|
|
|
|
|
|
.. code-block:: text
|
|
|
|
|
|
|
|
6
|
|
|
|
---
|
|
|
|
Level 3 | O |
|
|
|
|
/ --- \
|
|
|
|
/ | \
|
|
|
|
/ | \
|
|
|
|
/ | \
|
|
|
|
--- --- ---
|
|
|
|
Level 2 2 | E | 4 | D | | F | 5
|
|
|
|
--- --- ---
|
|
|
|
\ / \ /
|
|
|
|
\ / \ /
|
|
|
|
\ / \ /
|
|
|
|
--- ---
|
|
|
|
Level 1 1 | B | | C | 3
|
|
|
|
--- ---
|
|
|
|
\ /
|
|
|
|
\ /
|
|
|
|
---
|
|
|
|
Level 0 0 | A |
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
Notice that the class E, which is in the second level of the hierarchy,
|
|
|
|
precedes the class C, which is in the first level of the hierarchy, i.e.
|
|
|
|
E is more specialized than C, even if it is in a higher level.
|
|
|
|
|
|
|
|
A lazy programmer can obtain the MRO directly from Python 2.2, since in
|
|
|
|
this case it coincides with the Python 2.3 linearization. It is enough
|
|
|
|
to invoke the .mro() method of class A:
|
|
|
|
|
|
|
|
>>> A.mro() # doctest: +NORMALIZE_WHITESPACE
|
|
|
|
[<class 'A'>, <class 'B'>, <class 'E'>,
|
|
|
|
<class 'C'>, <class 'D'>, <class 'F'>,
|
|
|
|
<class 'object'>]
|
|
|
|
|
|
|
|
Finally, let me consider the example discussed in the first section,
|
|
|
|
involving a serious order disagreement. In this case, it is
|
|
|
|
straightforward to compute the linearizations of O, X, Y, A and B:
|
|
|
|
|
|
|
|
.. code-block:: text
|
|
|
|
|
|
|
|
L[O] = 0
|
|
|
|
L[X] = X O
|
|
|
|
L[Y] = Y O
|
|
|
|
L[A] = A X Y O
|
|
|
|
L[B] = B Y X O
|
|
|
|
|
|
|
|
However, it is impossible to compute the linearization for a class C
|
|
|
|
that inherits from A and B::
|
|
|
|
|
|
|
|
L[C] = C + merge(AXYO, BYXO, AB)
|
|
|
|
= C + A + merge(XYO, BYXO, B)
|
|
|
|
= C + A + B + merge(XYO, YXO)
|
|
|
|
|
|
|
|
At this point we cannot merge the lists XYO and YXO, since X is in the
|
|
|
|
tail of YXO whereas Y is in the tail of XYO: therefore there are no
|
|
|
|
good heads and the C3 algorithm stops. Python 2.3 raises an error and
|
|
|
|
refuses to create the class C.
|
|
|
|
|
|
|
|
Bad Method Resolution Orders
|
|
|
|
----------------------------
|
|
|
|
|
|
|
|
A MRO is *bad* when it breaks such fundamental properties as local
|
|
|
|
precedence ordering and monotonicity. In this section, I will show
|
|
|
|
that both the MRO for classic classes and the MRO for new style classes
|
|
|
|
in Python 2.2 are bad.
|
|
|
|
|
|
|
|
It is easier to start with the local precedence ordering. Consider the
|
|
|
|
following example:
|
|
|
|
|
|
|
|
>>> F=type('Food',(),{'remember2buy':'spam'})
|
|
|
|
>>> E=type('Eggs',(F,),{'remember2buy':'eggs'})
|
|
|
|
>>> G=type('GoodFood',(F,E),{}) # under Python 2.3 this is an error! # doctest: +SKIP
|
|
|
|
|
|
|
|
with inheritance diagram
|
|
|
|
|
|
|
|
.. code-block:: text
|
|
|
|
|
|
|
|
O
|
|
|
|
|
|
|
|
|
(buy spam) F
|
|
|
|
| \
|
|
|
|
| E (buy eggs)
|
|
|
|
| /
|
|
|
|
G
|
|
|
|
|
|
|
|
(buy eggs or spam ?)
|
|
|
|
|
|
|
|
|
|
|
|
We see that class G inherits from F and E, with F *before* E: therefore
|
|
|
|
we would expect the attribute *G.remember2buy* to be inherited by
|
|
|
|
*F.rembermer2buy* and not by *E.remember2buy*: nevertheless Python 2.2
|
|
|
|
gives
|
|
|
|
|
|
|
|
>>> G.remember2buy # doctest: +SKIP
|
|
|
|
'eggs'
|
|
|
|
|
|
|
|
This is a breaking of local precedence ordering since the order in the
|
|
|
|
local precedence list, i.e. the list of the parents of G, is not
|
|
|
|
preserved in the Python 2.2 linearization of G::
|
|
|
|
|
|
|
|
L[G,P22]= G E F object # F *follows* E
|
|
|
|
|
|
|
|
One could argue that the reason why F follows E in the Python 2.2
|
|
|
|
linearization is that F is less specialized than E, since F is the
|
|
|
|
superclass of E; nevertheless the breaking of local precedence ordering
|
|
|
|
is quite non-intuitive and error prone. This is particularly true since
|
|
|
|
it is a different from old style classes:
|
|
|
|
|
|
|
|
>>> class F: remember2buy='spam'
|
|
|
|
>>> class E(F): remember2buy='eggs'
|
|
|
|
>>> class G(F,E): pass # doctest: +SKIP
|
|
|
|
>>> G.remember2buy # doctest: +SKIP
|
|
|
|
'spam'
|
|
|
|
|
|
|
|
In this case the MRO is GFEF and the local precedence ordering is
|
|
|
|
preserved.
|
|
|
|
|
|
|
|
As a general rule, hierarchies such as the previous one should be
|
2024-05-26 05:20:37 -03:00
|
|
|
avoided, since it is unclear if F should override E or vice-versa.
|
2024-04-15 07:01:15 -03:00
|
|
|
Python 2.3 solves the ambiguity by raising an exception in the creation
|
|
|
|
of class G, effectively stopping the programmer from generating
|
|
|
|
ambiguous hierarchies. The reason for that is that the C3 algorithm
|
|
|
|
fails when the merge::
|
|
|
|
|
|
|
|
merge(FO,EFO,FE)
|
|
|
|
|
|
|
|
cannot be computed, because F is in the tail of EFO and E is in the tail
|
|
|
|
of FE.
|
|
|
|
|
|
|
|
The real solution is to design a non-ambiguous hierarchy, i.e. to derive
|
|
|
|
G from E and F (the more specific first) and not from F and E; in this
|
|
|
|
case the MRO is GEF without any doubt.
|
|
|
|
|
|
|
|
.. code-block:: text
|
|
|
|
|
|
|
|
O
|
|
|
|
|
|
|
|
|
F (spam)
|
|
|
|
/ |
|
|
|
|
(eggs) E |
|
|
|
|
\ |
|
|
|
|
G
|
|
|
|
(eggs, no doubt)
|
|
|
|
|
|
|
|
|
|
|
|
Python 2.3 forces the programmer to write good hierarchies (or, at
|
|
|
|
least, less error-prone ones).
|
|
|
|
|
|
|
|
On a related note, let me point out that the Python 2.3 algorithm is
|
|
|
|
smart enough to recognize obvious mistakes, as the duplication of
|
|
|
|
classes in the list of parents:
|
|
|
|
|
|
|
|
>>> class A(object): pass
|
|
|
|
>>> class C(A,A): pass # error
|
|
|
|
Traceback (most recent call last):
|
|
|
|
File "<stdin>", line 1, in ?
|
|
|
|
TypeError: duplicate base class A
|
|
|
|
|
|
|
|
Python 2.2 (both for classic classes and new style classes) in this
|
|
|
|
situation, would not raise any exception.
|
|
|
|
|
|
|
|
Finally, I would like to point out two lessons we have learned from this
|
|
|
|
example:
|
|
|
|
|
|
|
|
1. despite the name, the MRO determines the resolution order of
|
|
|
|
attributes, not only of methods;
|
|
|
|
|
|
|
|
2. the default food for Pythonistas is spam ! (but you already knew
|
|
|
|
that ;-)
|
|
|
|
|
|
|
|
Having discussed the issue of local precedence ordering, let me now
|
|
|
|
consider the issue of monotonicity. My goal is to show that neither the
|
|
|
|
MRO for classic classes nor that for Python 2.2 new style classes is
|
|
|
|
monotonic.
|
|
|
|
|
|
|
|
To prove that the MRO for classic classes is non-monotonic is rather
|
|
|
|
trivial, it is enough to look at the diamond diagram:
|
|
|
|
|
|
|
|
.. code-block:: text
|
|
|
|
|
|
|
|
|
|
|
|
C
|
|
|
|
/ \
|
|
|
|
/ \
|
|
|
|
A B
|
|
|
|
\ /
|
|
|
|
\ /
|
|
|
|
D
|
|
|
|
|
|
|
|
One easily discerns the inconsistency::
|
|
|
|
|
|
|
|
L[B,P21] = B C # B precedes C : B's methods win
|
|
|
|
L[D,P21] = D A C B C # B follows C : C's methods win!
|
|
|
|
|
|
|
|
On the other hand, there are no problems with the Python 2.2 and 2.3
|
|
|
|
MROs, they give both::
|
|
|
|
|
|
|
|
L[D] = D A B C
|
|
|
|
|
|
|
|
Guido points out in his essay [#]_ that the classic MRO is not so bad in
|
|
|
|
practice, since one can typically avoids diamonds for classic classes.
|
|
|
|
But all new style classes inherit from ``object``, therefore diamonds are
|
|
|
|
unavoidable and inconsistencies shows up in every multiple inheritance
|
|
|
|
graph.
|
|
|
|
|
|
|
|
The MRO of Python 2.2 makes breaking monotonicity difficult, but not
|
|
|
|
impossible. The following example, originally provided by Samuele
|
|
|
|
Pedroni, shows that the MRO of Python 2.2 is non-monotonic:
|
|
|
|
|
|
|
|
>>> class A(object): pass
|
|
|
|
>>> class B(object): pass
|
|
|
|
>>> class C(object): pass
|
|
|
|
>>> class D(object): pass
|
|
|
|
>>> class E(object): pass
|
|
|
|
>>> class K1(A,B,C): pass
|
|
|
|
>>> class K2(D,B,E): pass
|
|
|
|
>>> class K3(D,A): pass
|
|
|
|
>>> class Z(K1,K2,K3): pass
|
|
|
|
|
|
|
|
Here are the linearizations according to the C3 MRO (the reader should
|
|
|
|
verify these linearizations as an exercise and draw the inheritance
|
|
|
|
diagram ;-) ::
|
|
|
|
|
|
|
|
L[A] = A O
|
|
|
|
L[B] = B O
|
|
|
|
L[C] = C O
|
|
|
|
L[D] = D O
|
|
|
|
L[E] = E O
|
|
|
|
L[K1]= K1 A B C O
|
|
|
|
L[K2]= K2 D B E O
|
|
|
|
L[K3]= K3 D A O
|
|
|
|
L[Z] = Z K1 K2 K3 D A B C E O
|
|
|
|
|
|
|
|
Python 2.2 gives exactly the same linearizations for A, B, C, D, E, K1,
|
|
|
|
K2 and K3, but a different linearization for Z::
|
|
|
|
|
|
|
|
L[Z,P22] = Z K1 K3 A K2 D B C E O
|
|
|
|
|
|
|
|
It is clear that this linearization is *wrong*, since A comes before D
|
|
|
|
whereas in the linearization of K3 A comes *after* D. In other words, in
|
|
|
|
K3 methods derived by D override methods derived by A, but in Z, which
|
|
|
|
still is a subclass of K3, methods derived by A override methods derived
|
|
|
|
by D! This is a violation of monotonicity. Moreover, the Python 2.2
|
|
|
|
linearization of Z is also inconsistent with local precedence ordering,
|
|
|
|
since the local precedence list of the class Z is [K1, K2, K3] (K2
|
|
|
|
precedes K3), whereas in the linearization of Z K2 *follows* K3. These
|
|
|
|
problems explain why the 2.2 rule has been dismissed in favor of the C3
|
|
|
|
rule.
|
|
|
|
|
|
|
|
The end
|
|
|
|
-------
|
|
|
|
|
|
|
|
This section is for the impatient reader, who skipped all the previous
|
|
|
|
sections and jumped immediately to the end. This section is for the
|
|
|
|
lazy programmer too, who didn't want to exercise her/his brain.
|
|
|
|
Finally, it is for the programmer with some hubris, otherwise s/he would
|
|
|
|
not be reading a paper on the C3 method resolution order in multiple
|
|
|
|
inheritance hierarchies ;-) These three virtues taken all together (and
|
|
|
|
*not* separately) deserve a prize: the prize is a short Python 2.2
|
|
|
|
script that allows you to compute the 2.3 MRO without risk to your
|
|
|
|
brain. Simply change the last line to play with the various examples I
|
|
|
|
have discussed in this paper.::
|
|
|
|
|
|
|
|
#<mro.py>
|
|
|
|
|
|
|
|
"""C3 algorithm by Samuele Pedroni (with readability enhanced by me)."""
|
|
|
|
|
|
|
|
class __metaclass__(type):
|
|
|
|
"All classes are metamagically modified to be nicely printed"
|
|
|
|
__repr__ = lambda cls: cls.__name__
|
|
|
|
|
|
|
|
class ex_2:
|
|
|
|
"Serious order disagreement" #From Guido
|
|
|
|
class O: pass
|
|
|
|
class X(O): pass
|
|
|
|
class Y(O): pass
|
|
|
|
class A(X,Y): pass
|
|
|
|
class B(Y,X): pass
|
|
|
|
try:
|
|
|
|
class Z(A,B): pass #creates Z(A,B) in Python 2.2
|
|
|
|
except TypeError:
|
|
|
|
pass # Z(A,B) cannot be created in Python 2.3
|
|
|
|
|
|
|
|
class ex_5:
|
|
|
|
"My first example"
|
|
|
|
class O: pass
|
|
|
|
class F(O): pass
|
|
|
|
class E(O): pass
|
|
|
|
class D(O): pass
|
|
|
|
class C(D,F): pass
|
|
|
|
class B(D,E): pass
|
|
|
|
class A(B,C): pass
|
|
|
|
|
|
|
|
class ex_6:
|
|
|
|
"My second example"
|
|
|
|
class O: pass
|
|
|
|
class F(O): pass
|
|
|
|
class E(O): pass
|
|
|
|
class D(O): pass
|
|
|
|
class C(D,F): pass
|
|
|
|
class B(E,D): pass
|
|
|
|
class A(B,C): pass
|
|
|
|
|
|
|
|
class ex_9:
|
|
|
|
"Difference between Python 2.2 MRO and C3" #From Samuele
|
|
|
|
class O: pass
|
|
|
|
class A(O): pass
|
|
|
|
class B(O): pass
|
|
|
|
class C(O): pass
|
|
|
|
class D(O): pass
|
|
|
|
class E(O): pass
|
|
|
|
class K1(A,B,C): pass
|
|
|
|
class K2(D,B,E): pass
|
|
|
|
class K3(D,A): pass
|
|
|
|
class Z(K1,K2,K3): pass
|
|
|
|
|
|
|
|
def merge(seqs):
|
|
|
|
print '\n\nCPL[%s]=%s' % (seqs[0][0],seqs),
|
|
|
|
res = []; i=0
|
|
|
|
while 1:
|
|
|
|
nonemptyseqs=[seq for seq in seqs if seq]
|
|
|
|
if not nonemptyseqs: return res
|
|
|
|
i+=1; print '\n',i,'round: candidates...',
|
|
|
|
for seq in nonemptyseqs: # find merge candidates among seq heads
|
|
|
|
cand = seq[0]; print ' ',cand,
|
|
|
|
nothead=[s for s in nonemptyseqs if cand in s[1:]]
|
|
|
|
if nothead: cand=None #reject candidate
|
|
|
|
else: break
|
|
|
|
if not cand: raise "Inconsistent hierarchy"
|
|
|
|
res.append(cand)
|
|
|
|
for seq in nonemptyseqs: # remove cand
|
|
|
|
if seq[0] == cand: del seq[0]
|
|
|
|
|
|
|
|
def mro(C):
|
|
|
|
"Compute the class precedence list (mro) according to C3"
|
|
|
|
return merge([[C]]+map(mro,C.__bases__)+[list(C.__bases__)])
|
|
|
|
|
|
|
|
def print_mro(C):
|
|
|
|
print '\nMRO[%s]=%s' % (C,mro(C))
|
|
|
|
print '\nP22 MRO[%s]=%s' % (C,C.mro())
|
|
|
|
|
|
|
|
print_mro(ex_9.Z)
|
|
|
|
|
|
|
|
#</mro.py>
|
|
|
|
|
|
|
|
That's all folks,
|
|
|
|
|
|
|
|
enjoy !
|
|
|
|
|
|
|
|
|
|
|
|
Resources
|
|
|
|
---------
|
|
|
|
|
|
|
|
.. [#] The thread on python-dev started by Samuele Pedroni:
|
|
|
|
https://mail.python.org/pipermail/python-dev/2002-October/029035.html
|
|
|
|
|
|
|
|
.. [#] The paper *A Monotonic Superclass Linearization for Dylan*:
|
|
|
|
https://doi.org/10.1145/236337.236343
|
|
|
|
|
|
|
|
.. [#] Guido van Rossum's essay, *Unifying types and classes in Python 2.2*:
|
|
|
|
https://web.archive.org/web/20140210194412/http://www.python.org/download/releases/2.2.2/descrintro
|