Kodomo

Пользователь

Множественное наследование в питоне

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

   1 class C(A, B):
   2     ...

Зачем нужно множественное наследование

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

На эту тему есть примерно два ответа.

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

Например, если бы мы в такой парадигме делали библиотеку для рисования интерфейсов, мы бы создали класс Shape (фигура), сделали бы Rectangle потомком этого класса, создали бы класс WithText (то, на чём есть текст), и создали бы класс Clickable (то, на что можно нажать). И из этих классов мы могли бы сделать традиционную прямоугольную кнопку:

   1 class Shape(object):
   2     ...
   3 class Rectangle(Shape):
   4     ...
   5 class WithText(object):
   6     ...
   7 class Clickable(object):
   8     ...
   9 
  10 class Button(Clickable, Rectangle, WithText):
  11     ...

Некоторые люди умеют в этом стиле создавать большие библиотеки полуфабрикатов так, что потом для того, чтобы написать какую-нибудь, казалось бы, сложную задачу (например, веб-магазин), им достаточно унаследоваться от необходимого набора частей и добавить конфигурацию.

Второй ответ на вопрос, зачем нужно множественное наследование, примерно такой: если нам нужно сделать объект, который ведёт себя и как список, и как Tkinter.Canvas (например, поле для тетриса), то мы можем сделать новый класс, унаследовать его от одного из этих классов и добавить методов, чтобы он вёл себя дополнительно и как второй из этих классов, а можем просто унаследоваться сразу от обоих классов и добавить необходимого клея, чтобы состояние списка и состояние Canvas не рассинхронизировались.

   1 class TetrisField(list, Tkinter.Canvas):
   2     ...

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

Если переформулировать, этот ответ можно сказать так: иногда множественное наследование – это просто удобно, чтобы сделать быстро и чтобы работало.

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

FIXME: если у обоих родителей есть одноимённые методы; как должен себя вести одноимённый метод ребёнка?

FIXME: если дед один, а родителей два (diamond diagram)

FIXME: разных сущностей – столько, сколько ребёнку кажется, что у него предков, или меньше (если кто-нибудь из них совпадает)? – это не риторический вопрос, оба ответа имеют смысл

Алгоритм C3

FIXME: линеаризация; какими свойствами хотелось бы, чтобы она обладала:

FIXME: описание

FIXME: пример: линеаризация diamond diagram

FIXME: пример: нелинеаризуемое дерево наследования

Вызов методов родителей

FIXME: проблема метода init

Наивный подход

FIXME: наивный способ вызова методов родителей; его проблемы

super

FIXME: super; почему super устроен именно так (в питоне линеаризация хранится в классе.mro; в объекте хранится class_; в mro нужно найти, от какого места идти дальше)

FIXME: проблемы смешения двух подходов; пример

FIXME: набор правил, как лучше всего поступать в общем случае

Атрибуты объектов

FIXME: "атрибуты родителей" vs. "методы родителей"; слоёный пирог; есть ли здесь проблемы?

Заключительное слово

FIXME: из всего этого кажется, что множественное наследование – это очень сложно, но на самом деле, для того, чтобы упереться в какую-нибудь из проблем, нужно приложить очень много усилий

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

Ссылки