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

Давайте разберемся как работает вызов иерархических методов в Python.

Для начала самый простой вариант (в котором все понятно):

Есть иерархия классов (А это корень иерархии), заним потомки B и C, и самый низ иерархии D и E. Суть примера в том, что каждый из потомков вносит какое то изменение в функционал родителя. Имеем вот такую процедуру проверки:

Этот код принимает класс на входе, печатает его название, создает экземпляр класса и вызывает метод test().

Выполним код:

В результате получаем вполне ожидаемый вывод:

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

Простой пример ромбовидного наследования

Рассмотрим случай ромбовидного наследования, введём в иерархию еще два класса (забегая вперед два потому, что их поведение будет разнится в зависимости от порядка указания родителей)

Для этих двух классов вывод нашего теста будет следующим:

Самое интересное что стоит заменить, что были вызваны абсолютно все методы родителей (включая, что может показаться неожиданным, так сказать боковых родителей). Например для F1 сначала вызывается методы в класса D и B (это его прямые родители), потом “неожиданно”  E и C и затем только завершается все вызовом метода класса A.

И тут отметим первое правило при таких вызовах, каждый метод будет вызван только один раз.

Второй не менее интересный момент это наличие правила “линеаризации”, суть которого в том, что сначала обходится иерархия и выстраивается порядок вызовов, при построении порядка если метод встречается повторно то всегда остается более поздний вызов.  В примере с классом F1, метод класса А вызывается последним. Это происходит потому что сначала происходит обход иерархии первого класса D, в конце прохождения этой иерархии  мы имеем такую цепочку D->B->A, затем анализируется цепочка E->C->A, при их объединении метод класса А, встретится дважды именно второй вызов и останется. При этом если в заголовке сменить порядок следования классов родителей, как в примере с классом F2, то мы получим другой порядок сначала цепочка E->C->A, а затем цепочка D->B->A и конечный результат будет F2->E->C->D->B->A.

Управление направлением подъема по иерархии

Встроенный метод super с параметрами

Но что если в одной ситуации вы захотите пройти по левой цепочке, а в каких-то по правой? Обратимся к встроенному методу super. Его можно вызвать с параметрами передав в качестве параметра имя класса и ссылку.

Модифицируем пример классы F1 и F2, следующим образом:

И мы получим совсем другой вывод:

В этом случае цепочки стали короче, там нет вызова методов по соседней ветке иерархии. Однако, что странно куда то подевались вызовы непосредственных родителей для F1 это метод класса D, а для F2 – класса E. И вот тут нас ждет сюрприз, что super возвращает ссылку не указанного класса, а его родителя. Сразу оговорюсь что изменение вызова на return ‘F1_’ + super(F1, self).test() выдаст тот же результат, что и super вообще без параметров. 

Вызов метода через класс

Но а что делать если нужно полностью пройти по одной цепочке? В этом случае можно вспомнить, что методы класса можно вызвать непосредственно на самом классе и просто передать ему ссылку на объект, например:

Новые изменения в наши многострадальные классы привели к следующему выводу:

И тут все как мы хотели прошли по выбранной цепочке, при этом в примере специально не выбран первый из родителей, что бы показать, что цепочка идет по выбранному пути.

И все бы ничего, если бы ни несколько ложек дегтя, но об этом в следующей “серии”

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