Python: Полиморфизм
Теория: Полиморфизм (утиная типизация)
В программировании мы часто сталкиваемся с задачами, которые требуют разнообразных решений. Например, работа с объектами разных классов, у которых похожие или одинаковые методы.
Допустим, у нас могут быть классы Article и Topic, оба из которых имеют методы для добавления и получения комментариев. Нам нужно написать функцию, которая работает с обоими классами.
Для этого нам нужно найти способ обобщить код так, чтобы он мог работать с любым классом, который имеет нужные методы. В этом уроке мы рассмотрим эффективный способ решения этой задачи.
Обобщение методов для разных классов
Представим, что нам нужно написать функцию, которая проверяет, есть ли комментарии у статьи или топика. Статья в коде представлена объектом класса Article, а топик — Topic. На первый взгляд решить данную задачу можно так:
С подобным кодом мы уже сталкивались ранее. В нем мы используем условную конструкцию для определения типа объекта и вызова соответствующего метода.
Функция has_comments принимает объект commentable и в зависимости от его типа (Article или Topic) вызывает метод для получения комментариев (get_article_comments или get_topic_comments соответственно). Результатом выполнения функции является указание на наличие комментариев у объекта.
Классы Article и Topic имеют свои методы для получения комментариев: get_article_comments и get_topic_comments. Оба метода возвращают список комментариев к соответствующему объекту.
Подход, использованный в этом примере, не является оптимальным. При добавлении нового класса, имеющего комментарии, придется модифицировать функцию has_comments и добавить еще одну ветку условной конструкции. Это ведет к дублированию кода и увеличению сложности его поддержки.
Мы можем заменить этот подход на диспетчеризацию по ключу, но ситуация особо не улучшится. В любом случае придется описывать поведение для каждого класса в дополнение к тому, что поведение и так описано внутри каждого класса.
В данном случае мы сталкиваемся с проблемой: у нас есть разные классы объектов, которые имеют общее поведение. При этом мы не хотим, чтобы они наследовались от общего предка.
В такой ситуации нужно использовать утиную типизацию. Она помогает сделать код более гибким и обобщенным. Также она позволяет объектам взаимодействовать друг с другом, основываясь на поведении, а не на типе данных.
Применение утиной типизации
Утиная типизация или Duck typing — это подход, который используется в динамически типизированных языках, например Python. Он описывается следующим принципом: «Если это выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, и есть утка».
В контексте программирования это означает, что важен не сам тип объекта, а его поведение или методы, которые он реализует. Если объект ведет себя определенным образом, то он может быть использован вместо других объектов, которые ведут себя так же — независимо от их фактического типа.
Рассмотрим пример с двумя классами: Article и Topic. Оба класса имеют методы add_comment и get_comments, которые добавляют комментарии и возвращают список комментариев соответственно:
Мы можем написать функцию has_comments, которая проверяет, есть ли комментарии у объекта, используя метод get_comments. Эта функция будет работать как для объектов класса Article, так и для объектов класса Topic, поскольку оба класса реализуют метод get_comments:
Теперь посмотрим на практике, как эта функция работает в действии. В следующем примере has_comments будет применяться к объектам Article и Topic, демонстрируя принцип утиной типизации:
Здесь мы используем функцию has_comments к объектам классов Article и Topic и видим, что она корректно работает с обоими классами, так как они оба поддерживают метод get_comments. Это и есть утиная типизация в действии: функция has_comments работает с любым объектом, который «крякает как утка» — реализует метод get_comments.
Параметрический полиморфизм и полиморфизм подтипов
Способность функции обрабатывать объекты разных типов одинаковым образом, называется полиморфизмом подтипов, а сама функция – полиморфной функцией.
Как видно из кода выше, для реализации такого полиморфизма, в Python не нужно наследование и интерфейсы. В мире такой подход называют "утиной типизацией". Если что-то ходит как утка и крякает как утка, то это утка. Позже мы рассмотрим полиморфизм подтипов вместе с интерфейсами.
Технически, самое простое и понятное что делает полиморфизм подтипов (для клиентского кода) – убирает условные конструкции. Любую условную конструкцию можно заменить полиморфизмом и любую полиморфную функцию можно заменить ифами. Другими словами, полиморфизм подтипов не является неотъемлемой частью разработки, код можно писать и без него. С другой стороны, иногда встречаются ситуации, в которых он здорово помогает, но не сказать, что это происходит постоянно.
В чем же разница между параметрическим полиморфизмом и полиморфизмом подтипов? В первом случае, реализуется общий алгоритм для контейнера (например списка), который содержит значение или значения типа T. Этот алгоритм не зависит от T и для любых T выполняется идентично. Во втором, алгоритм построен вокруг самого объекта и использует его методы. В полиморфизме подтипов, полиморфная функция работает только с теми объектами, которые имеют необходимые для реализации алгоритма методы.
Выводы
Утиная типизация – это мощный инструмент в динамически типизированных языках, таких как Python. Она позволяет писать более гибкий и универсальный код, который работает с любыми объектами, предоставляющими определенный интерфейс, независимо от их типа.
Утиная типизация помогает писать более чистый, гибкий и модульный код. Но при этом стоит помнить, что не всегда нужно жертвовать ясностью и читаемостью кода ради его универсальности.

.png)
