Почему C3 MRO Python зависит от общего базового класса?

Когда я читаю о порядке разрешения метода Python C3, я часто слышу, что он сводится к «детям предшествуют родители, и порядок подклассов соблюдается». Тем не менее, это кажется верным только в том случае, если все подклассы наследуются от одного и того же предка.

E.g.

class X():
    def FOO(self):
        return 11

class A(X):
    def doIt(self):
        return super().FOO()        
    def FOO(self):
        return 42

class B(X):
    def doIt(self):
        return super().FOO()        
    def FOO(self):
        return 52

class KID(A,B):
    pass

Здесь MRO KID: KID, A, B, X

Однако, если я изменил B на:

class B(object):

MRO KID становится: KID, A, X, B.

Кажется, мы ищем суперкласс A до того, как закончим поиск всех родителей KID.

Так что сейчас это кажется немного менее интуитивным, чем «сначала дети, сначала широта», чем «сначала дети, сначала широта, если общий предок, иначе глубина сначала».

Было бы довольно сложно, если бы класс перестал использовать общего предка, MRO изменится (даже если общая иерархия такая же, за исключением этой ссылки), и вы начали вызывать метод более глубокого предка, а не тот, который находится в этом классе.


person aaa90210    schedule 14.09.2016    source источник
comment
C3 не зависит от общего базового класса. Python имеет имеет общий базовый класс (object), но это не является требованием C3. Наличие общего базового класса - очень веская причина для использования C3, поскольку он решает проблему с алмазом что подразумевает.   -  person Martijn Pieters    schedule 14.09.2016


Ответы (1)


Все классы в Python 3 имеют общий базовый класс object. Вы можете опустить класс из определения class, но он будет там, если вы уже не наследуете его косвенно от object. (В Python 2 вы должны явно наследовать от object, чтобы даже использовать super(), поскольку это функция класса нового стиля).

Вы изменили базовый класс B с X на object, но X также наследуется от object. MRO был изменен с учетом этого. То же упрощение правил C3 (потомки идут раньше родителей, и порядок подклассов соблюдается) здесь все еще применяется. B предшествует object, как и X, а A и B по-прежнему перечислены в том же порядке. Однако X должен стоять перед B, поскольку оба наследуются от object, а подкласс A(X) идет перед B в KID.

Обратите внимание, что нигде не сказано, что C3 сначала в ширину. Во всяком случае, это прежде всего глубина. См. Порядок разрешения методов Python 2.3 для получения подробное описание алгоритма и того, как он применяется к Python, но линеаризация любого класса является результатом объединения линеаризаций базовых классов и самих базовых классов:

L[KID] = KID + merge(L[A], L[B], (A, B))

где L[..] - линеаризация C3 этого класса (их MRO).

Таким образом, линеаризация A происходит перед B при слиянии, заставляя C3 смотреть на иерархии в глубину, а не вширь. Слияние начинается с самого левого списка и берет любой элемент, который не появляется в хвостах других списков (то есть все, кроме первого элемента), затем берет следующий и т. Д.

В вашем первом примере L[A] и L[B] почти одинаковы (они оба оканчиваются на (X, object), что и их MRO, только первый элемент отличается), поэтому слияние выполняется просто; вы объединяете (A, X, object) и (B, X, object), и их объединение дает вам только A из первого списка, затем весь второй список, заканчивающийся (KID, A, B, X, object) после добавления KID:

L[KID] = KID + merge((A, X, object), (B, X, object), (A, B))
#                        ^  ^^^^^^
#                         \ & \ both removed as they appear in the next list
       = KID + (A,) + (B, X, object)
       = (KID, A, B, X, object)

Во втором примере L[A] без изменений, но L[B] теперь (B, object) (отбрасывается X), поэтому при слиянии предпочтительнее X перед B, поскольку (A, X, object) идет первым при слиянии, а X не отображается во втором списке. Таким образом

L[KID] = KID + merge((A, X, object), (B, object), (A, B))
#                           ^^^^^^
#                            \removed as it appears in the next list
       = KID + (A, X) + (B, object)
       = (KID, A, X, B, object)
person Martijn Pieters    schedule 14.09.2016
comment
Я нахожу это еще более запутанным. Вы говорите, что все они имеют один и тот же базовый класс (объект), но MRO различается в зависимости от того, является ли родитель B прямым родителем A (X) или прародителем A (объектом). Представьте себе попытку выяснить изменение MRO в сложной реальной иерархии, когда какой-то класс в середине меняет родительский класс на класс всего на несколько уровней выше. - person aaa90210; 14.09.2016
comment
@ aaa90210: да, разработка линеаризации C3 в голове может вызвать головную боль. Это рекурсивное слияние, то есть сначала глубина, если тот же класс также не появляется в MRO следующего базового класса. - person Martijn Pieters; 14.09.2016
comment
@ aaa90210: это всегда делает object последним, поскольку это базовый класс всего. X "отодвинут" назад, потому что он является общим предком для A и B в вашем первом примере. Удаление его из B означает, что его можно объединить раньше. - person Martijn Pieters; 14.09.2016
comment
OK. Думаю, я буду осторожен с множественным наследованием. Спасибо! - person aaa90210; 14.09.2016