Зарегистрируйтесь, чтобы продолжить обучение

Полиморфизм (утиная типизация) Python: Полиморфизм

В программировании мы часто сталкиваемся с задачами, которые требуют разнообразных решений. Например, работа с объектами разных классов, у которых похожие или одинаковые методы.

Допустим, у нас могут быть классы Article и Topic, оба из которых имеют методы для добавления и получения комментариев. Нам нужно написать функцию, которая работает с обоими классами.

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

Обобщение методов для разных классов

Представим, что нам нужно написать функцию, которая проверяет, есть ли комментарии у статьи или топика. Статья в коде представлена объектом класса Article, а топик — Topic. На первый взгляд решить данную задачу можно так:

def has_comments(commentable):
    # Если это статья
    if isinstance(commentable, Article):
        return len(commentable.get_article_comments()) > 0
    # Если это топик
    elif isinstance(commentable, Topic):
        return len(commentable.get_topic_comments()) > 0


class Article:
    # some code

    def get_article_comments(self):
        return self.comments


class Topic:
    # some code

    def get_topic_comments(self):
        return self.comments


# метод first возвращает первый объект класса Article
article = Article.first()
print(has_comments(article))

С подобным кодом мы уже сталкивались ранее. В нем мы используем условную конструкцию для определения типа объекта и вызова соответствующего метода.

Функция 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, которые добавляют комментарии и возвращают список комментариев соответственно:

class Article:
    def __init__(self):
        self.comments = []

    def add_comment(self, comment):
        self.comments.append(comment)

    def get_comments(self):
        return self.comments


class Topic:
    def __init__(self):
        self.comments = []

    def add_comment(self, comment):
        self.comments.append(comment)

    def get_comments(self):
        return self.comments

Мы можем написать функцию has_comments, которая проверяет, есть ли комментарии у объекта, используя метод get_comments. Эта функция будет работать как для объектов класса Article, так и для объектов класса Topic, поскольку оба класса реализуют метод get_comments:

def has_comments(item):
    return len(item.get_comments()) > 0

Теперь посмотрим на практике, как эта функция работает в действии. В следующем примере has_comments будет применяться к объектам Article и Topic, демонстрируя принцип утиной типизации:

article = Article()
topic = Topic()

print(has_comments(article))  # => False

article.add_comment("Great article!")
print(has_comments(article))  # => True

print(has_comments(topic))  # => False

topic.add_comment("Interesting topic!")
print(has_comments(topic))  # => True

Здесь мы используем функцию has_comments к объектам классов Article и Topic и видим, что она корректно работает с обоими классами, так как они оба поддерживают метод get_comments. Это и есть утиная типизация в действии: функция has_comments работает с любым объектом, который «крякает как утка» — реализует метод get_comments.

Параметрический полиморфизм и полиморфизм подтипов

Способность функции обрабатывать объекты разных типов одинаковым образом, называется полиморфизмом подтипов, а сама функция – полиморфной функцией.

Как видно из кода выше, для реализации такого полиморфизма, в Python не нужно наследование и интерфейсы. В мире такой подход называют "утиной типизацией". Если что-то ходит как утка и крякает как утка, то это утка. Позже мы рассмотрим полиморфизм подтипов вместе с интерфейсами.

Технически, самое простое и понятное что делает полиморфизм подтипов (для клиентского кода) – убирает условные конструкции. Любую условную конструкцию можно заменить полиморфизмом и любую полиморфную функцию можно заменить ифами. Другими словами, полиморфизм подтипов не является неотъемлемой частью разработки, код можно писать и без него. С другой стороны, иногда встречаются ситуации, в которых он здорово помогает, но не сказать, что это происходит постоянно.

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

Выводы

Утиная типизация – это мощный инструмент в динамически типизированных языках, таких как Python. Она позволяет писать более гибкий и универсальный код, который работает с любыми объектами, предоставляющими определенный интерфейс, независимо от их типа.

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


Дополнительные материалы

  1. Полиморфизм простыми словами

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff