MRO(Method resolution order)
KarmanovichDevПорядок разрешения методов - механизм, который позволяет нам использовать наследование. Он требуется, что бы при обращении к атрибуту объекта понимать где и в каком порядке искать этот атрибут.

Как видите у ParentOfParent нет метода check, как и у его ближайшего родителя Parent. А что если сделать иерархию по сложнее?

И снова без каких либо проблем мы смогли найти метод check у Base? Хотя нам и пришлось обойти множество других классов. А что если у кого то из них метод check будет переопределён?

Как видите, в этот раз мы не дошли до Base, а остановились на ParentFour. Значит есть порядок проверки требуемого атрибута в рамках иерархии наследования. Кто же отвечает за то в каком порядке будет идти проверка классов на предмет существования требуемого нам атрибута? За это отвечает механизм MRO. Вот так будет выглядеть порядок поиска атрибута для иерархии классов из предыдущего примера:

Как же происходит определение порядка? Для этого используется алгоритм c3 линеаризации. Линеаризация в данном случае это превращение графа наследования в плоскую(линейную) последовательность.
Вот правила которых должен придерживаться алгоритм:
- Любой класс должен встретиться в списке лишь один раз
- Если класс X наследуется от нескольких классов, например, A, B, C, в таком же порядке они должны появиться в MRO. ["X", ..., "A", ... "B", ..., "C", ...] Между ними могут оказаться другие классы, но исходный порядок не должен быть нарушен.
- Родители класса X должны появляться по порядку старшинства. Сначала идут непосредственные родители, потом прародители и т. д., но не наоборот.
Наглядный пример из интернета, соответствующий этим правилам:

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

- Дважды вставить в список Base - ["FinalParent", "Base", "Parent", "Base", "object"]
- Изменить порядок - ["FinalParent", "Parent", "Base", "object"]
- Изменить старшинство - ["FinalParent", "Base", "Parent", "object"]
Всё это будет нарушать правила алгоритма линеаризации.
Это не единственная возможная ситуация когда линеаризация невозможна. Думаю показывать другие примеры будет излишним. В подобном случае вы всегда увидите ошибку до запуска вашей программы.

Вы так же можете сами влиять на то, какой порядок линеаризации будет в вашей иерархии с помощью MetaClass в котором будет определён метод mro, хотя в моей практике такого делать не приходилось. Если вам нужно такое переопределение, то есть большой шанс, что в вашей иерархии наследования изначально, что-то не так. Поэтому я бы не хотел показывать примеры кода как можно ломать схему наследования переопределяя его порядок своими руками 😊
Ну и не будем забывать про super(), с помощью которого так же можно управлять тем какой следующий родитель будет вызван, но этот вызов будет совершён в рамках текущего переопределённого метода. Повторюсь скорее всего вам это не нужно, и super() вы будете использовать без аргументов.
Стандартное использование super() требуется, что бы в требуемый момент вызвать реализацию родительского класса. Интерпретатор python сам подставит аргументы для вызова ближайшего родителя с требуемым атрибутом - магия вне Хогвартса!
