Method Resolution Order (MRO) in new-style classes?
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.
How Method Resolution Order (MRO) is working in this Python code
The documentation has a great explanation of how super()
works:
super([type[, object-or-type]])
Return a proxy object that delegates method calls to a parent or sibling class of type. This is useful for accessing inherited methods that have been overridden in a class.
The
object-or-type
determines the method resolution order to be searched. The search starts from the class right after the type.
For example, if
__mro__
ofobject-or-type
isD -> B -> C -> A -> object
and the value of type isB
, thensuper()
searchesC -> A -> object
.
super()
is equivalent to the expression super(__class__, self)
where __class__
is an class object within whose method the super()
is called. For example, super()
within grandchild.callingparent(self)
is essentially super(grandchild, self)
. And super()
inside child1.callchildform1(self)
function is super(child1, self)
.
MRO
for grandchild
is (<grandchild>, <child1>, <child2>, <parent>, <object>)
. Therefore, according to the above excerpt from documentation, when super().form1()
is called in child1.callchildform1()
, which is equivalent to super(child1, self)
, the search for the form1
method starts from the class right after the child1
in the MRO
sequence and the first matching class with form1
method is child2
.
This occurs due to that you are using the diamond inheritance structure and the principles that underlie the MRO:
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.
When designing such hierarchies, it is need to follow the approach of cooperative inheritance, an explanation can be found, in this already classic article.
Method resolution order in python
If you stop to think about it, it is just the intuitive way of working. This article, which by now just looks like an archaeological find, is still the authoritative description and reasoning on Python's method resolution order algorithm.
But despite the tecnical details in there, what happen in your two examples are:
In the first one, D,B,C,A
, the path through B indicates that A
's attribute should be used. But A
s attribute itself is shadowed by the attribute in C - that is, the declaration in C overrides the attr
declared in A
. Therefore it is the one used.
In the second hierarchy, D,B,C,A,E
, B comming first than C, again indicates that A.attr
should be used. This time, however, A's own attribute had not been shadowed by another class in the hierarchy - rather C.attr comes from another "lineage" - so the language picks the first it encounters.
That is the "plain english description" of what is going on. The authoritative article linked above lays the formal rules for that :
the linearization of [a class] C is the sum of C plus the merge of the
linearizations of the parents and the list of the parents.
...
[given class C(B1, ..., BN):], take the head of the first list, i.e L[B1][0] [linearization (aka mro) of Base B1 up to Object - the head is B1 -]; if this head is not in
the tail of any of the other lists [linearization lists for the other bases] , 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 [and subsequente versions] will refuse to create the class C and will raise an
exception.
Bringing in your second example, you haveD(B, C)
- the linearizations for B and C are: [B, A, object]
and [C, E, object]
and the linearization of D starts by taking "B", checking it is not
on the tail of any other lists (and it is not on [C, E, object]), then B is taken. The remaining lists are [A, object]
and [C, E, object]
- the algorithm then picks A
it is not in the other list, then A
is appended to the mro of D. It then picks object
. It is on the other list. So the algorithm leaves the first list intact, and takes C, E and finally object, for D, B, A, C, E, object
linearization.
In your first example, the linearization of both bases are [B, A, object]
and [C, A, object]
when the algorithm checks for A
, it is be on the tail of the second list - so, C
is picked first than A
from the second list - the final lineariation is D, B, C, A, object
.
How to customize the method resolution order (mro) of Python?
Your best bet is probably to have Third
explicitly use the Second
implementation:
class Third(First, Second):
set = Second.set
although the fact that you're asking this at all is a warning sign that you may have picked a bad class hierarchy.
How to check MRO (Method Resolution Order) in Python 2.7?
There is no explicit resolution order for old-style classes; instead, method resolution depends on the transitive closure of the set of parent classes.
From The Python 2.3 Method Resolution Order (emphasis mine):
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.
The resolution order, if exposed at all as explicit data, would have been an implementation detail, not part of the defined interface by the language.
Does the MRO of this class hierarchy is the same for old-style and new-style?
The thing is, whether in Python 2.2, 2.3, or later, these are all new-style classes. There are 3 method resolution schemes here, not 2:
- Classic class method resolution.
- Python 2.2 new-style class method resolution.
- Python 2.3+ C3 linearization.
Schemes 2 and 3 would produce the same MRO for the given example.
If you want to see the (now almost completely irrelevant) Python 2.2 method resolution scheme, the most complete documentation is probably a blog post from Guido's old Python history blog. There's also an old archived draft of documentation for Python 2.2, which mostly describes it but fails to mention a special case.
A consistent Method Resolution Order couldn't be created (MRO error)
childB
is attempting to inherit from Base
twice, once through childA
and once directly. Fix by removing the multiple inheritance on childB
.
class Base(object):
def __init__(self):
print ("Base")
class childA(Base):
def __init__(self):
print ('Child A')
Base.__init__(self)
class childB(childA):
def __init__(self):
print ('Child B')
super(childB, self).__init__()
b=childB()
Related Topics
What Is the Purpose of the -M Switch
How to Get User Ip Address in Django
How to Get Week Number in Python
What Are the Differences Between the Threading and Multiprocessing Modules
Temporarily Redirect Stdout/Stderr
Get Ip Address of Visitors Using Flask for Python
Why Do My Tkinter Widgets Get Stored as None
Hash Function in Python 3.3 Returns Different Results Between Sessions
Shared-Memory Objects in Multiprocessing
Python List VS. Array - When to Use
Intuition and Idea Behind Reshaping 4D Array to 2D Array in Numpy
How to Check If a Word Is an English Word with Python
How to Access the Ith Column of a Numpy Multidimensional Array