Search code examples
pythonpython-3.xmultiple-inheritancesuper

Calling super class method in multiple inheritance


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?


Solution

  • 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.