Today, I read the official doc of super.
In which it mentioned multiple inheritance will be decided by the __mro__
attribute of a class.
So I did a bit experiment, but its result surprised me.
# CODE PART
class GrandFather(object):
def p(self):
print "I'm old."
class Father(GrandFather):
def p(self):
print "I'm male."
class Mother(object):
def p(self):
print "I'm female."
class Son(Father, Mother):
def p(self):
print "busy, busy, crwaling. "
# EXPERIMENT PART
In [1]: Son.__mro__
Out[1]: (__main__.Son, __main__.Father, __main__.GrandFather, __main__.Mother, object)
In [2]: Father.__mro__
Out[2]: (__main__.Father, __main__.GrandFather, object)
In [3]: Mother.__mro__
Out[3]: (__main__.Mother, object)
In [4]: GrandFather.__mro__
Out[4]: (__main__.GrandFather, object)
In [5]: s = Son()
In [6]: super(Son, s).p()
I'm male.
In [7]: super(Father, s).p()
I'm old.
In [8]: super(Mother, s).p()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-8-ce4d0d6ef62d> in <module>()
----> 1 super(Mother, s).p()
AttributeError: 'super' object has no attribute 'p'
In [9]: super(GrandFather, s).p()
I'm female.
Below is part of the official doc I mentioned above, it says:
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 search order is same as that used by getattr() except that the type itself is skipped.
The __mro__ attribute of the type lists the method resolution search order
used by both getattr() and super().
The attribute is dynamic and can change whenever the inheritance hierarchy is updated.
If the second argument is an object, isinstance(obj, type) must be true.
By combining this doc and the result of my experiment. The most confusing part is that when calling with super(GrandFather, s).p()
it calls the p()
of Mother
, but Mother
isn't in GrandFather
's __mro__
, and it is on very inferior order of Son
's __mro__
.
After a bit pondering. I got a plausible explanation which indicate the incompleteness or deficiency of the official doc:
That is when using with super(type, instance)
, the super
function will search from the __mro__
attribute of the class
from who your instance
is build, but not the __mro__
attribute of the type
you passed to super
, even if it satisfied the isinstance(instance, type)
condition.
So what happened when you typed super(Class, instance)
is:
- Python check if
isinstance(instance, Class)
is True. - Python find the
__class__
attribute ofinstance
,
get the theinstance.__class__
's__mro__
attribute. - Python find the index of
Class
you passed tosuper
in the__mro__
tuple in step2. - Python add the index of step3 by 1, use it to get the corresponding class in
__mro__
tuple of step 2, and return the super delegate of this corresponding class. - If the index in step4 exceed length of
__mro__
of step2, the delegate of last class in__mro__
of step2 is returned, which is theobject
class.
Is my understanding right?
If I'm wrong, what's the correct mechanism that super
interacts with type
's __mro__
?
If I'm right, how should I raise an issue for python official doc modification?
Because I think the current version about this item could be misleading.
PS: This test was done by Python 2.7.6 within IPython 3.2.1
.
Look at the
__mro__
ofSon
:According to the doc:
So methods will be searched according to the order in the
__mro__
list, from left to right. Call ofsuper(type, instance)
will change the starting position to the type specified as the first argument ofsuper()
in the__mro__
list of the class of the instance specified as the second argument (if the second argument passed to super is a instance):super(Son, s)
will proxy to__main__.Father
super(Father, s)
will proxy to__main__.GrandFather
super(GrandFather, s)
will proxy to__main__.Mother
super(Mother, s)
will proxy toobject
The interesting part is why
__mro__
ofSon
is like it is. In other words why Mother is after GrandFather. This is because of how the linearization is working in python:See the examples in the documentation you mentioned, it explains a very similar case.
So that final result is actually correct:
super(GrandFather, s).p()
should beI'm female.
From chapt 32 of learning python:
so for
super(cls, instance)
(isinstance(instance, cls)
must beTrue
), the method is selected from the next class ininstance.__class__.__mro__
starting frominstance.__class__
.for
super(cls0, cls1)
(issubclass(cls1, cls0)
must beTrue
), the method is selected from next class incls1.__mro__
starting fromcls0
in both cases, if the method is not implemented by the next class in the MRO chain, the search will skip ahead until it finds a class with the method defined.