Какой порядок является порядком разрешения метода?

Это порядок, в котором python ищет метод в иерархии классов. Это особенно полезно, когда мы имеем дело с множественным наследованием. Важность разрешения метода не очевидна, пока мы не имеем дело с ситуацией, когда оно нам абсолютно необходимо.

Где именно он нужен?

Предположим, у нас есть следующая ситуация. У нас есть класс отца и класс матери. И у нас есть дочерний класс, который наследуется как от отца, так и от матери в указанном порядке.

Какая функция будет вызываться в этом случае, когда вызывается child.do_something(), поскольку и у отца, и у матери есть функция do_something? Это именно тот тип ситуации, когда нам нужно линеаризовать порядок классов в иерархии, чтобы мы могли определить, чьи методы или атрибуты имеют приоритет над другими.

Как получить MRO класса?

Чтобы отобразить порядок разрешения методов класса, мы можем либо получить доступ к атрибуту __mro__ класса, либо вызвать функцию Child.mro(). Это даст порядок разрешения метода. Из этого мы видим, что будет вызван метод do_something отца, поскольку это первый класс в MRO, который имеет метод do_something.

Простой способ выяснить порядок в основных случаях

На самом деле вы можете определить MRO в простых случаях, просто посмотрев, как он наследуется, используя схему «сначала в глубину слева направо».

  • Первым фактором в определении приоритета является глубина наследования, т. е. насколько далеко в иерархии находится класс. Это теория о том, что дочерние классы переопределяют методы своих родителей, что довольно стандартно.
  • Второй фактор, который следует учитывать, — это положение классов в одном поколении. Здесь приоритет идет слева направо. В нашем примере class Child(Father, Mother) отцовские классы предшествуют материнским.

Таким образом, принимая во внимание два вышеупомянутых фактора, MRO нашего класса Child в приведенном выше примере будет следующим объектом: Ребенок › Отец › Мать › объект.

C3 Линеаризация

Теперь мы пытаемся копнуть немного глубже в то, как работает алгоритм. В более сложной иерархии классов определить MRO не так-то просто. Хотя я сказал, что вы можете использовать схему «сначала в глубину слева направо» для определения MRO в простых случаях для нашего удобства, фактический алгоритм, используемый для определения MRO в python3, — это линеаризация C3. Линеаризация C3 приводит к трем важным свойствам:

  • последовательно расширенный граф приоритетов
  • сохранение локального порядка старшинства
  • монотонный порядок

Определение

  • Линеаризация C3 класса представляет собой сумму
    * самого класса плюс
    * уникального слияния линеаризации его родителей плюс список родителей
  • Слияние линеаризации родителя выполняется путем выбора первой главы списка, которая не появляется в конце каких-либо других списков. Выбранный заголовок переносится из списка слияния в список вывода.
  • Второй шаг повторяется до тех пор, пока все классы не попадут из списка слияния в выходной список.

Сделаем сериализацию C3 сами

Случай множественного наследования приведен ниже. Давайте вычислим MRO класса K в следующем примере.

Решение:

First, let's get the linearization of the base class
L(object) = [object] --Eq1 // since it is has no base class it's linearization list only has itself
Now, to get the linearization of 1st generations.
From the definition of [C3 Linearization, we can write, L(A) = [A] + merge(L(object), [object])`
L(A) = [A] + merge(L(object), [object]) 
     = [A] + merge([object], [object]) // from [Eq1]
     = [A, object] // object added to the output list because it's the only head doesn't appear in any tail
Similarly, for B & C,
L(B) = [B, object] 
L(C) = [C, object]
Now, let's calculate the linearization for K
From the definition of [C3 Linearization], L(K) is class + unique merge of (linearizations of parents + list of parent from left to right),
L(K) = [K] + merge(L(A), L(B), L(C), [A, B, C])                      
     = [K] + merge([A, object], [B, object], [C, object], [A, B, C]) // Replacing all the L(A), L(B), L(C) with their actual value
     = [K, A] + merge([object], [B, object], [C, object], [B, C])    // Added A to the output list because it only appears in the head of all list in the merge part
     = [K, A, B] + merge([object], [object], [C, object], [C])       // Skipped object (going from left to right) and added B to the output list because it only appears in the head of all list in the merge part
     = [K, A, B, C] + merge([object], [object], [object])
     = [K, A, B, C, object]

Мы можем проверить правильность нашего решения, вызвав функцию .mro().

Можете ли вы представить себе ситуацию, когда множественное наследование будет нарушено?

Поскольку линеаризация C3 требует сохранения локального порядка приоритета. Когда линеаризация C3 нарушается, это обычно происходит из-за неправильного выбора конструкции. Один из таких примеров приведен ниже:

Порядок A и B в наследовании M и N обратный. Это не вызывает никаких проблем. Но когда X наследуется как от M, так и от N, локальный приоритет не сохраняется в иерархии класса X. Следовательно, это приведет к ошибке со следующим сообщением об ошибке.

Заключение

Итак, MRO — довольно полезная концепция в Python и любом языке программирования, поддерживающем множественное наследование. Это порядок, в котором python ищет методы в иерархии классов. И в этом блоге мы немного узнали о его внутренней работе. Вы можете прочитать больше об этой теме по ссылкам, указанным в разделе ссылок.

Рекомендации

ТОиР не кто иной, как сам Гвидо ван Россум

Линеаризация C3