tags:

views:

262

answers:

3

In the book "Python in a Nutshell" (2nd Edition) there is an example which uses
old style classes to demonstrate how methods are resolved in classic resolution order and
how is it different with the new order.

I tried the same example by rewriting the example in new style but the result is
no different than what was obtained with old style classes. The python version
I am using to run the example is 2.5.2. Below is the example:

class Base1(object):  
    def amethod(self): print "Base1"  

class Base2(Base1):  
    pass

class Base3(object):  
    def amethod(self): print "Base3"

class Derived(Base2,Base3):  
    pass

instance = Derived()  
instance.amethod()  
print Derived.__mro__

The call "instance.amethod()" prints "Base1", but as per my understanding of the MRO
with new style of classes the output should have been "Base3". The
call "Derived.mro" prints:

(<class '__main__.Derived'>, <class '__main__.Base2'>, <class '__main__.Base1'>, <class '__main__.Base3'>, <type 'object'>)

I am not sure if my understanding of MRO with new style classes is incorrect or
that I am doing a silly mistake which I am not able to detect. Please help me
in better understanding of MRO.

+2  A: 

The result you get is correct. Try changing base class of Base3 to Base1 and compare with the same hierarchy for classic classes:

class Base1(object):
    def amethod(self): print "Base1"

class Base2(Base1):
    pass

class Base3(Base1):
    def amethod(self): print "Base3"

class Derived(Base2,Base3):
    pass

instance = Derived()
instance.amethod()


class Base1:
    def amethod(self): print "Base1"

class Base2(Base1):
    pass

class Base3(Base1):
    def amethod(self): print "Base3"

class Derived(Base2,Base3):
    pass

instance = Derived()
instance.amethod()

Now it outputs:

Base3
Base1

Read this explanation for more information.

Denis Otkidach
A: 

You're seeing that behavior because method resolution is depth-first, not breadth-first. Dervied's inheritance looks like

         Base2 -> Base1
        /
Derived - Base3

So instance.amethod()

  1. Checks Base2, doesn't find amethod.
  2. Sees that Base2 has inherited from Base1, and checks Base1. Base1 has a amethod, so it gets called.

This is reflected in Derived.__mro__. Simply iterate over Derived.__mro__ and stop when you find the method being looked for.

jamessan
I doubt that the reason I get "Base1" as answer is because method resolution is depth-first,I think there is more to it than a depth-first approach. See Denis's example, if it were depth first o/p should have been "Base1". Also refer to the first example in the link you have provided, there also the MRO shown indicates that the method resolution is not just determined by traversing in depth-first order.
sateesh
Sorry the link to the document on MRO is provided by Denis. Please check that, I mistook that you provided me the link to python.org.
sateesh
It's generally depth-first, but there are smarts to handle diamond-like inheritance as Alex explained.
jamessan
+7  A: 

The crucial difference between resolution order for legacy vs new-style classes comes when the same ancestor class occurs more than once in the "naive", depth-first approach -- e.g., consider a "diamond inheritance" case:

>>> class A: x = 'a'
... 
>>> class B(A): pass
... 
>>> class C(A): x = 'c'
... 
>>> class D(B, C): pass
... 
>>> D.x
'a'

here, legacy-style, the resolution order is D - B - A - C - A : so when looking up D.x, A is the first base in resolution order to solve it, thereby hiding the definition in C. While:

>>> class A(object): x = 'a'
... 
>>> class B(A): pass
... 
>>> class C(A): x = 'c'
... 
>>> class D(B, C): pass
... 
>>> D.x
'c'
>>>

here, new-style, the order is:

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

with A forced to come in resolution order only once and after all of its subclasses, so that overrides (i.e., C's override of member x) actually work sensibly.

It's one of the reasons that old-style classes should be avoided: multiple inheritance with "diamond-like" patterns just doesn't work sensibly with them, while it does with new-style.

Alex Martelli