One question as of yet unanswered is how does Python determine
the __mro__ for a type? A basic idea behind the
algorithm is provided in this section. This is not essential for just
using super, or reading following sections, so you
can jump to the next section if you want.
Python determines the precedence of types
(or the order in which they should be placed in any
__mro__) from two kinds of constraints specified by
the user:
If
Ais a superclass ofB, thenBhas precedence overA. Or,Bshould always appear beforeAin all__mro__s (that contain both). In short let's denote this asB > A.If
Cappears beforeDin the list of bases in a class statement (eg.class Z(C,D):), thenC > D.
In addition, to avoid being ambiguous, Python adheres to the following principle:
If
E > Fin one scenario (or one__mro__), then it should be thatE > Fin all scenarios (or all__mro__s).
We can satisfy the constraints if we build the
__mro__ for each new class C we
introduce, such that:
All superclasses of
Cappear in theC.__mro__(plusCitself, at the start), andThe precedence of types in
C.__mro__does not conflict with the precedence of types inB.__mro__for eachBinC.__bases__.
Here the same problem is translated into a game. Consider a class hierarchy as follows:
Since only single inheritance is in play, it is easy to find the
__mro__ of these classes. Let's say we define a new
class as class N(A,B,C). To compute the
__mro__, consider a game using abacus style beads
over a series of strings.
Beads can move freely over the strings, but the strings cannot be cut
or twisted. The strings from left to right contain beads in the order
of __mro__ of each of the bases. The rightmost
string contains one bead for each base, in the order the bases are
specified in the class statement.
The objective is to line up beads in rows, so that each row contains
beads with only one label (as done with the O bead
in the diagram). Each string represents an ordering constraint, and if
we can reach the goal, we would have an order that satisfies all
constraints. We could then just read the labels off rows from the
bottom up to get the __mro__ for
N.
Unfortunately, we cannot solve this problem. The last two strings have
C and B in different
orders. However, if we change our class definition to class
N(A,C,B), then we have some hope.
We just found out that N.__mro__ is
(N,A,C,B,object) (note we inserted
N at the head). The reader can try out this
experiment in real Python (for the unsolvable case above, Python
raises an exception). Observe that we even swapped the position of two
strings, keeping the strings in the same order as the bases are
specified in the class statement. The usefulness of this is seen
later.
Sometimes, there might be more than one solution, as shown in
the figure below. Consider four classes class
A(object), class B(A), class
C(object) and class D(C). If a new class
is defined as class E(B, D), there are multiple
possible solutions that satisfy all constraints.
Possible positions for A are shown as the
little beads. The order can be kept unambiguous (more
correctly, monotonic) if the following policies
are followed:
Arrange strings from left to right in order of appearance of bases in the class statement.
Attempt to arrange beads in rows moving from bottom up, and left to right. What this means is that the MRO of
class E(B, D)will be set to:(E,B,A,D,C,object). This is becauseA, being left ofC, will be selected first as a candidate for the second row from bottom.
This, essentially, is the idea behind the algorithm used by
Python to generate the __mro__ for any new
type. The formal algorithm is formally explained elsewhere [mro-algorithm].



