0

I've two examples of multiple inheritance that's look like the same but I'm getting differents results order.

First Example from Joël:

class A(object):
    def t(self):
        print 'from A'

class B(object):
    def t(self):
        print 'from B'

class C(A): pass

class D(C, B): pass

And as a result we've:

>>> d = D()
>>> d.t() # Will print "from A"

>>> D.__mro__
(<class '__main__.D'>, <class '__main__.C'>, <class '__main__.A'>, 
<class '__main__.B'>, <type 'object'>)

Then in the Second Example from Callisto:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First):
    def __init__(self):
        print "third"

class Fourth(Second, Third):
    def __init__(self):
        super(Fourth, self).__init__()
        print "that's it"

And as a result we've:

>>> f = Fourth()
second
that's it

>>> Fourth.__mro__
(<class '__main__.Fourth'>, <class '__main__.Second'>, <class '__main__.Third'>
<class '__main__.First'>, <type 'object'>)

As you can see, the flow order of MRO are different, in the Second Example it don't reach First before Third but in the First Example it pass by A before going to B.

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
Rafael Capucho
  • 461
  • 6
  • 13
  • What's your question? – Eric Apr 23 '13 at 18:16
  • @Eric: the question is: why in the Second Example it don't reach First before Third but in the First Example it pass by A before going to B? – aldeb Apr 23 '13 at 18:18
  • @segfolt: Yes, If you follow the MRO you'll see that the behavior (order) are different but the code are equivalent, in my point of view the MRO ordem should be the same. – Rafael Capucho Apr 23 '13 at 18:21
  • 1
    @RafaelCapucho: In what way is the code equivalent? – Eric Apr 23 '13 at 18:22

3 Answers3

3

There is no inconsistency. The MRO is based on the C3 algorithm which is explained by Chaturvedi and more formally by Simionato.


Regarding:

in the Second Example it don't reach First before Third but in the First Example it pass by A before going to B.

Since Third is defined by

class Third(First):

Third must show up before First in the MRO.

Chaturvedi explains this with the rule,

If A is a superclass of B, then B has precedence over A. Or, B should always appear before A in all __mro__s (that contain both).

While in the First example, the equivalent of First and Third is A and C. Since C is defined by

class C(A):

C comes before A in the MRO.

Why A comes before B is more complicated. It is ultimately due to C being listed before B in the bases of D. In Simionato's notation,

L[D] = L[D(C,B)] = D + merge(L[C], L[B], CB)
     = D + merge(CAO, BO, CB)
     = D + C + merge(AO, BO, B)
     = D + C + A + merge(O, BO, B)
     = D + C + A + B + merge(O, O)
     = D + C + A + B + O

and in the second example,

L[4] = L[4(2, 3)] = 4 + merge(L[2], L[3], 23)
     = 4 + merge(21O, 31O, 23)
     = 4 + 2 + merge(1O, 31O, 3)
     = 4 + 2 + 3 + merge(1O, 1O)    # Third must come before First
     = 4 + 2 + 3 + 1 + merge(O, O)
     = 4 + 2 + 3 + 1 + O

The operative rule being:

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.

(My emphasis explains why Third must come before First).

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • Then, In my second example, the order `4 -> 2 -> 3 -> 1` occurs because i reach the instruction right in the `2` classes, if there aren't one `__init__` instruction in the `2` so it'll try the classe `1` before backtracking to `4` right? I guess that i got, they'll do somekind of left recursion until reach the instruction or the end and after that jump to right sibiling param. – Rafael Capucho Apr 23 '13 at 18:48
  • I'm sorry, but I'm having difficulty following what you are asking. I suggest reading [Chaturvedi](http://www.cafepy.com/article/python_attributes_and_methods/python_attributes_and_methods.html) to understand the flavor of the algorithm, and to read [Simionato](http://www.python.org/2.3/mro.html) for the details. Then refer the equations in my post where I show all the gory details for your two examples. – unutbu Apr 23 '13 at 18:57
  • @unutbu: I'm reading the Simionato's tutorial. In the tutorial, I don't understand the transition:`A + B + merge(DEO,CDFO,C) = A + B + C + merge(DEO,DFO)`. In my opinion it should be `= A + B + D + merge(EO, CFO, C)` because of the operative rule. I'd be very thankful if you could explain the transition. thanks! – aldeb Apr 23 '13 at 19:52
  • @segfolt: `D` shows up in the tail of `CDFO`, so `D` can not be used as the next class in the linearization. So the algorithm goes on to try the next head, which is `C`. `C` is not in the tail of any of the other lists in the mergo, so it is a "good head". `merge(DEO, CDFO, C) = C + merge(DEO, CFO, C)`. – unutbu Apr 23 '13 at 19:56
  • @unutbu: Thanks a lot! I omitted that the tail is all the list excepted the head. I thought it was the last element of the list. – aldeb Apr 23 '13 at 20:04
  • @unutbu: Btw: according to Simionato's tutorial, it should be `L[D(C,B)] = D + merge(L[C], L[B], CB)` rather than `L[D(C,B)] = D + merge(L[C], L[B], C, B)` in your post, no? – aldeb Apr 23 '13 at 20:09
0

It's not the same, in the first example, all "dad" objects inherits object, as the D loads C that loads A and, in the order of MRO, B (as invoked by D)

In second, ALL "dad" objects inherits the class First. At this time, Python load First in last as it's need to process Second and Third.

PS: It's my theory, but it's just logic.

Here is an running example of what you implemented in his examples: http://ideone.com/pfzXiG

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First):
    def __init__(self):
        print "third"

class Fourth(Second, Third):
    def __init__(self):
        super(Fourth, self).__init__()
        print "that's it"

print "First: "
print Fourth.__mro__

class First(object):
    def __init__(self):
        print "first"

class Second(object):
    def __init__(self):
        print "second"

class Third(First):
    def __init__(self):
        print "third"

class Fourth(Third, Second):
    def __init__(self):
        super(Fourth, self).__init__()
        print "that's it"
print "Second:"
print Fourth.__mro__
Fernando Jorge Mota
  • 1,506
  • 1
  • 10
  • 13
-1

Your first inheritance heirarchy looks like this:

D
|\
C B
| |
A |
 \|
   object

Whereas your second is:

4
|\
2 3
 \|
  1
  |
  object

why in the Second Example it don't reach First before Third but in the First Example it pass by A before going to B?

Why would you expect these to behave the same way? Looking at the graphs, the relationship between A and B is completely different to that between First and Third

Eric
  • 95,302
  • 53
  • 242
  • 374