I have the following code:
class A:
pass
class B(A):
def foo(self, a):
if a:
return 'B'
return super(B, self).foo(a)
class C:
def foo(self, a):
return 'C'
class D(B, C):
def foo(self, a):
return super().foo(a)
d = D()
print(d.foo(0))
When I call d.foo(0)
based on MRO it first calls the foo
method of B
class and inside that, if the condition is wrong and it will return super(B, self).foo(0)
but class A
has no foo method and I expect this error:
AttributeError: 'super' object has no attribute 'foo'
but it returns 'C'
from class C
. Why?
super()
searches the MRO for the next class that has the attribute; that A
doesn't implement it doesn't matter as C
is still considered.
For D
, the MRO is D
, B
, A
, C
:
>>> D.__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.A'>, <class '__main__.C'>, <class 'object'>)
so super().foo
in D
will find B.foo
, and from B.foo
, A
is skipped and C.foo
is found; you can test this yourself from the interactive interpreter:
>>> super(D, d).foo
<bound method B.foo of <__main__.D object at 0x1079edb38>>
>>> super(B, d).foo
<bound method C.foo of <__main__.D object at 0x1079edb38>>
This is what a Python implementation of the attribute search algorithm would look like:
def find_attribute(type_, obj, name):
starttype = type(obj)
mro = iter(starttype.__mro__)
# skip past the start type in the MRO
for tp in mro:
if tp == type_:
break
# Search for the attribute on the remainder of the MRO
for tp in mro:
attrs = vars(tp)
if name in attrs:
res = attrs[name]
# if it is a descriptor object, bind it
descr = getattr(type(res), '__get__', None)
if descr is not None:
res = descr(
res,
None if obj is starttype else obj,
starttype)
return res
where type_
is the first argument to super()
(the class the method is defined on), obj
is the instance (so type(d)
here), and name
is the attribute you are looking for.