In Python 2.7, I am trying to reconstruct AN inheritance chain from a certain class E
to the root A
. There is a diamond inheritance problem as shown below, but I am interested in a path, not THE path, so it should work. Whether it's something I should be doing (this way) is questionable, but right now I just want to know what I am misunderstanding...
class A(object):
@classmethod
def supah(thisCls):
return [cls for cls in thisCls.__bases__ if issubclass(cls, A)]
def name(self):
return 'A'
class C(A):
def name(self):
return 'C'
class D(A):
def name(self):
return 'D'
class E(C, D):
def name(self):
return 'E'
current = E
while True:
try:
print current, super(current, E()), super(current, E()).name()
except AttributeError:
break
current = current.supah()[0]
The output
<class '__main__.E'> <super: <class 'E'>, <E object>> C
<class '__main__.C'> <super: <class 'C'>, <E object>> D
<class '__main__.A'> <super: <class 'A'>, <E object>>
What is D doing there? It is calling
super(C, E()).name()
where super(C, E())
should be "class A", right? If the C on the first line had been an D I would have (sort of) understood, but in my mind the second line should definitely be an A.
Any help?
EDIT: My understanding was that calling
super(C, obj).name()
would result in the name "A", because the linearization of C is [C, A, object]
.
However, this is not what super(C, obj).name()
means apparently. It still uses the full linearization of obj: [E, C, D, A, object]
(thanks to @Martijn Pieters), it just starts at (after) C. Therefore D comes before A.
super()
doesn't look at __bases__
; it looks at the Method Resolution Order (MRO), through type(self).mro()
:
>>> E.mro()
[<class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.A'>, <type 'object'>]
As you can see, D
is in there, because it is a base class of E
; when you call super(C, E()).name()
, D
comes next in the MRO.
The MRO will always include all base classes in a hierarchy; you cannot build a class hierarchy where the MRO could not be established. This to prevent classes being skipped in a diamond inheritance pattern.
How the MRO works is explained in detail in The Python 2.3 Method Resolution Order.
You may also want to read Guido van Rossum's explanation; he uses a diamond pattern with:
class A:
def save(self): pass
class B(A): pass
class C(A):
def save(self): pass
class D(B, C): pass
to illustrate why an MRO is important; when calling D().save()
you'd want C.save()
to be invoked (more specialized), not A.save()
.
If you really wanted to skip D
from C.name
, you'd have to explicitly find C.__bases__[0]
in the MRO, then tell super()
to start search for the next .name()
method from there:
mro = type(self).mro()
preceding = mro[0]
for i, cls in enumerate(mro[1:], 1):
if cls in self.__bases__:
preceding = mro[i - 1]
name = super(preceding, self).name()
For your E.mro()
and class C
, this'll find D
, as it precedes the first base class of C
, A
. Calling super(D, self).name()
then tells super()
to find the first class past D
with a name()
method, which is A
here.