Что такое полиморфизм в python
Перейти к содержимому

Что такое полиморфизм в python

  • автор:

Наследование

Наследование — это способность объекта принимать одну или несколько характеристик от других классов объектов, обычно переменных или функций-членов. Можно провести аналогию между этим понятием и наследственностью как передачей характерных черт от родителей к потомству. Например, у ребенка могут быть глаза отца и улыбка матери. Глаза отца и улыбка матери — это черты, которые ребенок наследует от своих родителей. Они выглядят/проявляются одинаково у родителей и потомства.

Информатика использует понятие наследования при создании классов, между которыми устанавливаются отношения “род-вид”. В отношениях “род-вид” один объект связан с другим объектом. Например, собака — это домашнее животное, сельдерей — овощ, а Марс — планета. В отношениях “род-вид” есть две сущности: родитель и ребенок. Родитель в этих отношениях является общей версией ребенка. Собака (ребенок) — это домашнее животное (родитель). В информатике мы называем родительский класс в отношениях “род-вид” “суперклассом”, а дочерний — “подклассом”. Подкласс наследует методы и/или переменные от суперкласса. Теперь напишем код для отношений dog/pet (собака/домашнее животное).

Создание подкласса

Обратите внимание на то, что в приведенных примерах для создания класса Dog мы заключаем Pet в скобки в объявлении класса: Dog(Pet) . Использование этого синтаксиса позволяет сообщить Python, что класс, который мы создаем, является подклассом суперкласса Pet. Поскольку ни один из методов и переменных в классе Pet не является закрытым, подкласс Dog сможет получить доступ ко всем элементам класса Pet. Именно так экземпляр собаки способен вызывать методы feedme() и eat() .

Некоторые атрибуты класса могут быть созданы закрытыми. Создать переменную закрытой означает, что этот атрибут не будет доступен подклассу. Имейте это в виду при проектировании классов!

Полиморфизм

Теперь, когда вы получили представление о наследовании, можно перейти к понятию “полиморфизм”. Полиморфизм — это способность чего-либо иметь несколько форм. Вернемся к предыдущему примеру. Все домашние животные в конечном итоге нуждаются в еде, но способ, которым они питаются, может отличаться. Например, попугай будет есть, клюя корм для птиц, в то время как собака будет есть, пережевывая собачий корм. И попугай, и собака — домашние животные, которые едят пищу, но разница в том, что они едят разную пищу по-разному. Взглянем на пример в коде.

В этом примере собака и попугай переопределяют метод eat(self,food_name) . Переопределение метода означает, что метод в подклассе переопределяет метод, указанный в суперклассе. В этом суть полиморфизма: один и тот же метод, но разные характеристики в зависимости от класса.

Заключение

Мы кратко рассмотрели наследование и полиморфизм в Python. Поначалу эти понятия, возможно, будет непросто усвоить. Лучший способ справиться с этим — разработать классы, которые отражают сущности реального мира, а затем идентифицировать свойственные им отношения “род-вид”.

Полиморфизм в python#

Полиморфизм в python реализован во многих ипостасях.

Ad-hoc полиморфизм#

Ad-hoc полиморфизм пример полиморфизма, который не свойственен для python , т.к. перегружать функции честным образом в нем нельзя: каждое следующее объявление функции с таким же именем затрет предыдущее.

Рассмотрим пример такого полиморфизма в C++ .

В данном примере функция double_it как бы векторизована: её можно вызывать и для одного целого числа и сразу для вектора целых чисел.

В python нельзя перегружать функции по типу параметров. Лучшее что вы можете сделать — проверить в runtime, какого типа аргумент, и в соответствии с этим проделать необходимые операции.

В данном примере, если аргумент не целое число и не список, то возбуждается исключение TypeError , чтобы в наибольшей степени воспроизвести поведение кода из C++ .

singledispatch #

В ряде ситуаций удобно применить singledispatch из модуля functools, который позволяет как бы перегружать функции по типу первого аргумента.

Сначала объявляется и декорируется наиболее общая функция, которую называют generic . Это приводит к тому, что создаётся объект-обертка с именем этой функции, у которого есть метод-декоратор register . С помощью него регистрируются все остальные реализации этой функции для разного типа первого параметра.

Перегруженные версии функции объявляются с именем, отличным от имени исходной функции. Обычно это "_" .

При вызове функции объект-обертка сравнивает тип первого параметра с зарегистрированными функциями и на основе этой информации вызывает подходящую реализацию. Если подходящей реализация не находится, то вызывается исходная generic функция.

Декоратора для множественной диспетчеризации в стандартной библиотеке python нет, но существует сторонняя библиотека multidispatch.

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

Параметрический полиморфизм в C++ реализован через шаблонные функции.

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

Компилятор C++ сгенерирует свою версию шаблонной функции для каждого типа данных, с котором встретит вызов это функции, что может привести к кратному увеличению объёма получаемого машинного кода. Такой эффект получил известность как «раздувание кода».

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

В python большинство функций можно считать параметрически полиморфными. В функцию add ниже можно передать аргументы любых типов. Функция успешно вернет значение для любых аргументов, для которых выражение x + y не возбуждает ошибку. При этом в отличие от примера с C++ эту функцию можно вызывать с аргументами разных типов.

Итого, проектируя функцию вы всегда ориентируетесь лишь на интерфейс объектов, которые будут в неё переданы. Часто говорят, что в python работает утиная типизация.

Полиморфизм подтипа#

Здесь функция print_area заранее не знает, какую конкретную реализацию метода area она будет вызывать. Это определяется в runtime в зависимости от истинного типа объекта по указателю. В итоге функция print_area может обрабатывать все подклассы базового класса Shape одинаково, что и считается полиморфным поведением. При этом в отличие от шаблонных функций для виртуальных функций не генерируется свой машинный код для каждого подкласса, все по-настоящему обрабатывается одним и тем же кодом.

В python почти все методы класса проявляют виртуальные свойства. Воспроизведем пример из C++ в python .

Как уже обсуждалось, в python обычно роль играет лишь интерфейс или поведение объекта, а не его тип, а значит предыдущий пример мог быть реализован и без наследования: можно было объявить классы Circle и Square без базовых классов, тогда функция print_area работала бы так же, если бы у обоих классов реализован метод area .

Абстрактные базовые классы, как средство достижения полиморфизма#

Тем не менее полиморфизм подтипа полезен в python не только, чтобы явно обозначить иерархию между типами данных, или, чтобы напомнить программисту реализовать определенный интерфейс, но и для более совершенной реализации Ad-hoc полиморфизма. Это достигается за счет того, что встроенная функция isinstance возвращает True и для объектов производных классов.

В качестве примера рассмотрим рассмотренную выше реализацию функции double_it .

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

К счастью, для таких целей существуют абстрактный базовый класс Number из модуля numbers. Все встроенные числовые типы данных наследует от этого базового класса.

А модуль collections кроме очень полезных контейнеров определяет абстрактные базовые классы для коллекций в подмодуле collections.abc. Для целей примера выше сгодится абстрактный базовый класс Collection.

Отредактируем исходный пример, чтобы он работал с любыми числами и любыми коллекциями чисел.

Полиморфизм в Python

В этой статье мы изучим полиморфизм, разные типы полиморфизма и рассмотрим на примерах как мы можем реализовать полиморфизм в Python.

Что такое полиморфизм?

В буквальном значении полиморфизм означает множество форм.

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

Давайте посмотрим на пример:

Пример 1: полиморфизм оператора сложения

Мы знаем, что оператор + часто используется в программах на Python. Но он не имеет единственного использования.

Для целочисленного типа данных оператор + используется чтобы сложить операнды.

Итак, программа выведет на экран 3 .

Подобным образом оператор + для строк используется для конкатенации.

В результате будет выведено Python Programming .

Здесь мы можем увидеть единственный оператор + выполняющий разные операции для различных типов данных. Это один из самых простых примеров полиморфизма в Python.

Полиморфизм функций

В Python есть некоторые функции, которые могут принимать аргументы разных типов.

Одна из таких функций — len() . Она может принимать различные типы данных. Давайте посмотрим на примере, как это работает.

Пример 2: полиморфизм на примере функции len()

Вывод:

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

Полиморфизм функции len()

Полиморфизм функции len()

Полиморфизм в классах

Полиморфизм — очень важная идея в объектно-ориентированном программировании.

Чтобы узнать больше об ООП в Python, посетите эту статью: Python Object-Oriented Programming.

Мы можем использовать идею полиморфизма для методов класса, так как разные классы в Python могут иметь методы с одинаковым именем.

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

Пример 3: полиморфизм в методах класса

Вывод:

Здесь мы создали два класса Cat и Dog . У них похожая структура и они имеют методы с одними и теми же именами info() и make_sound() .

Однако, заметьте, что мы не создавали общего класса-родителя и не соединяли классы вместе каким-либо другим способом. Даже если мы можем упаковать два разных объекта в кортеж и итерировать по нему, мы будем использовать общую переменную animal . Это возможно благодаря полиморфизму.

Полиморфизм и наследование

Как и в других языках программирования, в Python дочерние классы могут наследовать методы и атрибуты родительского класса. Мы можем переопределить некоторые методы и атрибуты специально для того, чтобы они соответствовали дочернему классу, и это поведение нам известно как переопределение метода(method overriding).

Полиморфизм позволяет нам иметь доступ к этим переопределённым методам и атрибутам, которые имеют то же самое имя, что и в родительском классе.

Давайте рассмотрим пример:

Пример 4: переопределение метода

Вывод:

Здесь мы можем увидеть, что такие методы как __str__() , которые не были переопределены в дочерних классах, используются из родительского класса.

Благодаря полиморфизму интерпретатор питона автоматически распознаёт, что метод fact() для объекта a (класса Square ) переопределён. И использует тот, который определён в дочернем классе.

С другой стороны, так как метод fact() для объекта b не переопределён, то используется метод с таким именем из родительского класса( Shape ).

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

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

Заметьте, что перегрузка методов(method overloading) — создание методов с одним и тем же именем, но с разными типами аргументов не поддерживается в питоне.

# Polymorphism — полиморфизм

Полиморфизм в объектно-ориентированном программировании – это возможность обработки разных типов данных, т.е. принадлежащих к разным классам, с помощью «одно и той же» функции, или метода. На самом деле одинаковым является только имя метода, его исходный код зависит от класса. Кроме того, результаты работы одноименных методов могут существенно различаться. Поэтому в данном контексте под полиморфизмом понимается множество форм одного и того же слова – имени метода.

Например, два разных класса содержат метод total, однако инструкции каждого предусматривают совершенно разные операции. Так в классе T1 – это прибавление 10 к аргументу, в T2 – подсчет длины строки символов. В зависимости от того, к объекту какого класса применяется метод total, выполняются те или иные инструкции:

Мы уже наблюдали полиморфизм между классами, связанными наследованием. У каждого может быть свой метод __init__() или square() или какой-нибудь другой. Какой именно из методов square() вызывается, и что он делает, зависит от принадлежности объекта к тому или иному классу.

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

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

В Python среди прочего полиморфизм находит отражение в методах перегрузки операторов. Два из них мы уже рассмотрели. Это __init__() и __del__() , которые вызываются при создании объекта и его удалении. Полиморфизм у методов перегрузки операторов проявляется в том, что независимо от типа объекта, его участие в определенной операции, вызывает метод с конкретным именем. В случае __init__() операцией является создание объекта.

Рассмотрим пример полиморфизма на еще одном методе, который перегружает функцию print().

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

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

Какую именно строку возвращает метод str(), дело десятое. Он вполне может строить квадратик из символов:

Вывод в консоль:

Полиморфизм является еще одним базовым аспектом объектно-ориентированного программирования и предполагает способность к изменению функционала, унаследованного от базового класса.

Например, пусть у нас будет следующая иерархия классов:

В производном классе Employee, который представляет служащего, определяется свой конструктор. Так как нам надо устанавливать при создании объекта еще и компанию, где работает сотрудник. Для этого конструктор принимает четыре параметра: стандартный параметр self, параметры name и age и параметр company.

В самом конструкторе Employee вызывается конструктор базового класса Person. Обращение к методам базового класса имеет следующий синтаксис:

Поэтому в конструктор базового класса передаются имя и возраст. Сам же класс Employee добавляет к функционалу класса Person еще один атрибут — self.company.

Кроме того, класс Employee переопределяет метод display_info() класса Person, поскольку кроме имени и возраста необходимо выводить еще и компанию, в которой работает служащий. И чтобы повторно не писать код вывода имени и возраста здесь также происходит обращение к методу базового класса — методу get_info: Person.display_info(self).

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

В основной части программы создается список из трех объектов Person, в котором два объекта также представляют классы Employee и Student. И в цикле этот список перебирается, и для каждого объекта в списке вызывается метод display_info. На этапе выполнения программы Python учитывает иерархию наследования и выбирает нужную версию метода display_info() для каждого объекта. В итоге мы получим следующий консольный вывод:

# Проверка типа объекта

При работе с объектами бывает необходимо в зависимости от их типа выполнить те или иные операции. И с помощью встроенной функции isinstance() мы можем проверить тип объекта. Эта функция принимает два параметра:

Первый параметр представляет объект, а второй — тип, на принадлежность к которому выполняется проверка. Если объект представляет указанный тип, то функция возвращает True. Например, возьмем выше описанную иерархию классов:

Вывод в консоль:

# Упражнения

Написать класс стол который принимает параметры: высота, ширина, высота, тип (круглый или квадратный).

Написать два класса «круглый стол» и «прямоугольный стол» на следующих основной класс «Стол». И добавить в новые классы методы расчёта площади столешницы по соответствующим формулам.

Написать один класс «Стол» включающим поле «форма столешницы» с методом «площадь столешницы», который вычисляет площадь в зависимости от выбранной формы.

Написать интерфейс программы для столов содержащий, используя генерацию интерфейса:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *