Вызов метода суперкласса при множественном наследовании

У меня есть следующий код:

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))

Когда я вызываю d.foo(0) на основе MRO, он сначала вызывает метод foo класса B и внутри него, если условие неверно, и он возвращает super(B, self).foo(0), но класс A не имеет метода foo, и я ожидаю эту ошибку:

AttributeError: 'super' object has no attribute 'foo'

но он возвращает 'C' из класса C. Почему?


person Saeed    schedule 29.07.2017    source источник
comment
В этом весь смысл super. Подумайте об этом - если бы он вызывал родителя напрямую, вам вообще не понадобился бы super в языке, вы бы просто вызвали A прямо здесь.   -  person wim    schedule 29.07.2017


Ответы (1)


super() ищет в MRO следующий класс, имеющий атрибут; что A не реализует, это не имеет значения, так как C все еще считается.

Для D ТОиР D, B, A, C:

>>> D.__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.A'>, <class '__main__.C'>, <class 'object'>)

поэтому super().foo в D найдет B.foo, а из B.foo пропускается A и находится C.foo; вы можете проверить это самостоятельно из интерактивного интерпретатора:

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

Вот как будет выглядеть Python-реализация алгоритма поиска атрибутов:

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

где type_ — первый аргумент super() (класс, в котором определен метод), obj — экземпляр (здесь type(d)), а name — искомый атрибут.

person Martijn Pieters    schedule 29.07.2017