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.