Как работают иерархии классов в Python (часть 2)

В первой части мы разобрали, основы того, как происходит вызов классов в иерархии Python. Давайте же рассмотрим обещанные интересности. 

Отсутствие обращения к родителю

Изменим пример из первой части. Пускай класс B полностью реализует всю функциональность без обращения к родителю (я не призываю так делать без четкого осознания, что вы делаете и зачем, но такое возможно), наш новый пример примет вид (приведу его полностью):

Запуск этого тестового примера выдаст на экран следующий результат:

Результаты для классов A, C, E – остались не изменёнными, вызов методов для B, D – теперь не приводит к вызову метода А (так как метод в классе B больше не обращается к родителю). Но куда более интересное поведение мы имеем с классами F1 и F2.  Для класса F1 мы получили короткую цепочку F1->D->B (тут вызов методов оборвался, причем он не пошел дальше по соседней цепочке). Класс F2 также демонстрирует “странное” поведение его цепочка более логична F2->E->C->D->B, но в ней отсутствует вызов метода класса A из иерархии E -> C-> A. Хотя правило линеаризации подсказывало, что метод бы должен был быть.

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

Переопределенные методы

Что бы проиллюстрировать следующую особенность немного видео изменим исходный пример (из первой статьи):

В этом примере в родительском классе мы ввели дополнительный метод, который вызывается из основного, а затем переопределили его поведение ниже по иерархии. При запуске получим следующий результат:

В приведенном выводе стоит обратить внимание на следующие в иерархии, при поиске методов используете тот же самый алгоритм реалинизации (но при этом используется первый найденный метод). Для иерархии F1->D->B->E->C->A был вызван метод из класса В. В то время для иерархии F2->E->C->D->B->A был вызван метод класса Е.

Управление иерархией вызовов

При этом если рассмотреть пример с уточнением вызова родителей:

То результат по выбору переопределенного метода будет совпадать с предыдущим експериментом:

Стоит отметить, что не смотря на то, что фактически вызов метода из класса С не происходил, Python все равно выбирал переопределенный метод из него (это связано с тем, что поиск метода начинается с текущего класса и идет вверх по родителям, его наше переопределение вызовов родителей не затронуло).

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

Получим вот такой результат:

Тут тоже поиск вызываемого метода происходил снизу иерархии. При этом если бы, например класс F1 переопределил метод response_method, то был бы вызван именно он.

Вариант более сложной иерархии

Давайте немного усложним пример, заменим классы F одним классом, введем еще уровни в иерархию:

Результат выполнения этой программы будет довольно большим но интересным:

Самые интересные результаты демонстрируют  нижние классы в иерархии M1 и M2. Цепочка вызовов M2->J->G->F->D->B->E->C->A (при этом будет вызван переопределенный метод из класса В). Цепочка вызовов M1->G->J->F->D->B->E->C->A (при этом будет вызван переопределенный метод из класса В). Вызов метода из класса В можно объяснить тем, что предки класса F расположены в порядке сначала D затем E (поэтому сначала всегда анализируются методы из цепочки по D). Куда более интересно обратить внимание на то, что происходит сначала вызов метода родителя, затем его соседа, а уже затем их общего родителя.

Обсуждение закрыто.