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

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

  • автор:

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

Полиморфизм — это способность выполнять действие над объектом независимо от его типа. Это обычно реализуется путем создания базового класса и наличия двух или более подклассов, которые все реализуют методы с одинаковой сигнатурой. Любая другая функция или метод, который манипулирует этими объектами, может вызывать одни и те же методы независимо от того, с каким типом объекта он работает, без предварительной проверки типа. В объектно-ориентированной терминологии, когда класс X расширяет класс Y, Y называется суперклассом или базовым классом, а X называется подклассом или производным классом.

class Shape: """Это родительский класс, который предназначен для наследования другими классами.""" def calculate_area(self): """Этот метод предназначен для переопределения в подклассах. Если подкласс не реализует его, но вызывается, будет поднят NotImplemented.""" #raise NotImplementedError("Not implemented") class Square(Shape): """Это подкласс класса Shape, представляющий собой квадрат.""" side_length = 2 # в этом примере стороны имеют длину 2 единицы def calculate_area(self): """Этот метод переопределяет Shape.calculate_area(). Когда у объекта типа Square есть свой метод calculate_area(), это метод, который будет вызываться, а не версия родительского класса. Он выполняет расчет, необходимый для этой формы, квадрата и возвращает результат.""" return self.side_length * 2 class Triangle(Shape): """Это также подкласс класса Shape, и он представляет собой треугольник.""" base_length = 4 height = 3 def calculate_area(self): """Этот метод также переопределяет Shape.calculate_area() и выполняет вычисление площади. расчет для треугольника, возвращающий результат.""" return 0.5 * self.base_length * self.height def get_area(input_obj): """ Эта функция принимает входной объект и вызывает функцию этого объекта. метод calculate_area(). Обратите внимание, что тип объекта не указан. Это может быть объектом Square, Triangle или Shape. """ print(input_obj.calculate_area()) # Создадим по одному объекту каждого класса shape_obj = Shape() square_obj = Square() triangle_obj = Triangle() # Теперь передайте каждый объект по одному функции get_area() и посмотрите результат. get_area(shape_obj) get_area(square_obj) get_area(triangle_obj)

Мы должны увидеть этот вывод:

None 4 6.0

Что происходит без полиморфизма? Без полиморфизма может потребоваться проверка типа перед выполнением действия над объектом, чтобы определить правильный метод для вызова. Следующий пример счетчика выполняет ту же задачу, что и предыдущий код, но без использования полиморфизма, то get_area() функция должна делать больше работы.

class Square: side_length = 2 def calculate_square_area(self): return self.side_length ** 2 class Triangle: base_length = 4 height = 3 def calculate_triangle_area(self): return (0.5 * self.base_length) * self.height def get_area(input_obj): # Обратите внимание на проверки типов, которые здесь необходимы. Эти проверки типа # может стать очень сложным для более сложного примера, что приведет к # дублирующийся и сложный в сопровождении код. if type(input_obj).__name__ == "Square": area = input_obj.calculate_square_area() elif type(input_obj).__name__ == "Triangle": area = input_obj.calculate_triangle_area() print(area) # Создадим по одному объекту каждого класса square_obj = Square() triangle_obj = Triangle() # Теперь передайте каждый объект, по одному, в функцию get_area() и просмотрите результат. get_area(square_obj) get_area(triangle_obj)

Мы должны увидеть этот вывод:

4 6.0

Важная заметка Обратите внимание, что классы, используемые в примере счетчика, являются классами «нового стиля» и неявно наследуются от класса объекта, если используется Python 3. Полиморфизм будет работать как в Python 2.x, так и в 3.x, но код контрпримера полиморфизма вызовет исключение, если он будет выполнен в интерпретаторе Python 2.x из-за типа (input_obj). имя будет возвращать «экземпляр» вместо имени класса , если они явно не наследуют от объекта, в результате чего в области никогда не быть назначен.

Duck Typing

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

class Duck: def quack(self): print("Quaaaaaack!") def feathers(self): print("The duck has white and gray feathers.") class Person: def quack(self): print("The person imitates a duck.") def feathers(self): print("The person takes a feather from the ground and shows it.") def name(self): print("John Smith") def in_the_forest(obj): obj.quack() obj.feathers() donald = Duck() john = Person() in_the_forest(donald) in_the_forest(john)
Quaaaaaack! The duck has white and gray feathers. The person imitates a duck. The person takes a feather from the ground and shows it.

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

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

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

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

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

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

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

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

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

num1 = 1 num2 = 2 print(num1 + num2)

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

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

str1 = "Python" str2 = "Programming" print(str1+" "+str2)

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

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

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

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

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

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

print(len("Programiz")) print(len(["Python", "Java", "C"])) print(len())

Вывод:

9 3 2

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

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

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

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

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

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

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

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

class Cat: def __init__(self, name, age): self.name = name self.age = age def info(self): print(f"I am a cat. My name is . I am years old.") def make_sound(self): print("Meow") class Dog: def __init__(self, name, age): self.name = name self.age = age def info(self): print(f"I am a dog. My name is . I am years old.") def make_sound(self): print("Bark") cat1 = Cat("Kitty", 2.5) dog1 = Dog("Fluffy", 4) for animal in (cat1, dog1): animal.make_sound() animal.info() animal.make_sound()

Вывод:

Meow I am a cat. My name is Kitty. I am 2.5 years old. Meow Bark I am a dog. My name is Fluffy. I am 4 years old. Bark

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

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

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

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

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

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

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

from math import pi class Shape: def __init__(self, name): self.name = name def area(self): pass def fact(self): return "I am a two-dimensional shape." def __str__(self): return self.name class Square(Shape): def __init__(self, length): super().__init__("Square") self.length = length def area(self): return self.length**2 def fact(self): return "Squares have each angle equal to 90 degrees." class Circle(Shape): def __init__(self, radius): super().__init__("Circle") self.radius = radius def area(self): return pi*self.radius**2 a = Square(4) b = Circle(7) print(b) print(b.fact()) print(a.fact()) print(b.area())

Вывод:

Circle I am a two-dimensional shape. Squares have each angle equal to 90 degrees. 153.93804002589985

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

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

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

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

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

Полиморфизм и абстрактные методы

Полиморфизм – это возможность работы с совершенно разными объектами (языка Python) единым образом.

Кажется, пока не особо понятно? Поэтому давайте, как всегда, постигнем суть этого подхода на конкретном примере.

Вначале я продемонстрирую пример, где мы увидим один недостаток, который как раз исправляется с помощью полиморфизма. Предположим, у нас есть два класса Rectangle и Square:

class Rectangle: def __init__(self, w, h): self.w = w self.h = h def get_rect_pr(self): return 2*(self.w+self.h) class Square: def __init__(self, a): self.a = a def get_sq_pr(self): return 4*self.a

И в них объявлены геттеры get_rect_pr() и get_sq_pr() для получения периметра соответствующих фигур: прямоугольника и квадрата. Далее, мы можем создать экземпляры этих классов и вывести в консоль значения периметров:

r1 = Rectangle(1, 2) r2 = Rectangle(3, 4) print(r1.get_rect_pr(), r2.get_rect_pr()) s1 = Square(10) s2 = Square(20) print(s1.get_sq_pr(), s2.get_sq_pr())

Все отлично, все работает. Но, теперь предположим, что все эти объекты помещаются в коллекцию:

geom = [r1, r2, s1, s2]

которую можно легко перебрать с помощью цикла for и где бы мы хотели получить значение периметра для каждой фигуры:

for g in geom: print(g.get_rect_pr())

Как вы понимаете, когда в цикле очередь дойдет до объекта s1, возникнет ошибка, т.к. в классе Square отсутствует метод get_rect_pr(). Конечно, зная, что в коллекции находятся объекты Rectangle и Square, можно было бы в цикле записать проверку:

for g in geom: if isinstance(g, Rectangle): print(g.get_rect_pr()) else: print(g.get_sq_pr())

и все заработает. Но у такого кода мало гибкости и, например, при добавлении еще одного класса:

class Triangle: def __init__(self, a, b, c): self.a = a self.b = b self.c = c def get_tr_pr(self): return self.a + self.b + self.c

Получим снова ошибку:

t1 = Triangle(1,2,3) t2 = Triangle(4,5,6) geom = [r1, r2, s1, s2, t1, t2]

Конечно, в цикле for можно дополнительно проверить на соответствие классам Square и Triangle, но красоты и гибкости нашей программе это не придаст. Вот как раз здесь очень хорошо применим подход, который и называется полиморфизмом. Мы договоримся в каждом классе создавать методы с одинаковыми именами, например,

Тогда в цикле будем просто обращаться к этому методу и получать периметры соответствующих фигур:

for g in geom: print( g. get_pr() )

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

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

geom = [Rectangle(1, 2), Rectangle(3, 4), Square(10), Square(20), Triangle(1, 2, 3), Triangle(4, 5, 6) ]

Мне кажется, так программа выглядит несколько приятнее и читабельнее.

Абстрактные методы

Но у нашей реализации есть один существенный недостаток. Что если мы забудем в каком-либо классе определить метод get_pr(), например, в Triangle. Тогда, очевидно, программа приведет к ошибке. Как можно было бы этого избежать? Один из вариантов определить базовый класс для классов геометрических примитивов и в нем прописать реализацию геттера get_pr(), используемую по умолчанию, например, так:

class Geom: def get_pr(self): return -1

А все остальные классы унаследовать от него:

class Rectangle(Geom): . class Square(Geom): . class Triangle(Geom): .

Теперь, после запуска программы, для треугольников будем получать значения -1.

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

class Geom: def get_pr(self): raise NotImplementedError("В дочернем классе должен быть переопределен метод get_pr()")

И если в каком-либо дочернем классе не будет определен метод get_pr(), то вызовется метод базового класса и выдаст ошибку NotImplementedError, которая будет сигнализировать о том, что метод не переопределен.

Запустим программу и действительно видим это сообщение при попытке вызвать get_pr() для объектов Triangle. Причем, видя ошибку NotImplementedError, мы понимаем, что она связана именно с необходимостью переопределения get_pr(), а не с чем-то другим. В этом плюс такого подхода.

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

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

Видео по теме

Концепция ООП простыми словами

Полиморфизм в «Пайтон»

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

Полиморфизм в ООП — что это?

Если говорить буквально, то слово polymorphism означает «множество форм».

Полиморфизм в «Пайтон»

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

Пример № 1

Хорошо известно, что оператор “+” нередко применяют в программах, написанных на Python. Но использовать этот оператор можно по-разному.

Если мы говорим о целочисленных типах данных, то мы применяем “+” в целях сложения операндов:

number1 = 1 number2 = 2 print(number1 + number2)

Такая программа выведет на экран цифру 3. Элементарно, Ватсон!

Однако применять “+” можно и для конкатенации строк:

string1 = "Hello," string2 = "Otus!" print(string1+" "+string2)

Результат очевиден и здесь:

Какой же вывод можно сделать из вышесказанного? У нас существует единственный оператор “+”, который способен выполнять разные операции для разных типов данных. Это является одним из наиболее простых примеров полиморфизма на «Пайтон».

Пример № 2

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

print(len("ООП-программирование")) print(len(["Python", "Java", "C#", "Scala", "C++"])) print(len())

Вывод будет необычен, но если разобраться, то все просто:

20 5 2

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

Полиморфизм в «Пайтон»

  • посчитала количество букв в слове «Программирование»;
  • посчитала количество слов в списке;
  • посчитала количество ключей в словаре.

Пример № 3

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

Ниже — пример такого полиморфизма в методах класса:

Полиморфизм в «Пайтон»

Смотрим вывод в консоль:

Мяу! Я кот. Меня зовут Васька. Мне 2 года. Мяу! Гав! Я собака. Меня зовут Мухтар. Мне 3 года. Гав!

У нас создано 2 класса: Cat и Dog. У этих классов структура похожа, плюс они имеют методы с одинаковыми именами:

  • make_sound() ;
  • info() .

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

Пример № 4

Как и в прочих языках программирования, в «Питоне» классы-потомки способны выполнять наследование методов и атрибутов родительского класса. То есть у нас существует возможность переопределить ряд methods и attributes, сделав это для того, чтобы они соответствовали классу-потомку. Данное поведение называют переопределением (overriding). И благодаря наличию полиморфизма мы можем получать доступ к переопределенным methods и attributes, имеющим такое же имя, как и в parent class.

Пример такого переопределения ниже:

from math import pi class Shape: def __init__(self, name): self.name = name def area(self): pass def fact(self): return "Я - двумерная фигура" def __str__(self): return self.name class Square(Shape): def __init__(self, length): super().__init__("Квадрат") self.length = length def area(self): return self.length**2 def fact(self): return "Любой угол квадрата равен 90 градусов." class Circle(Shape): def __init__(self, radius): super().__init__("Круг") self.radius = radius def area(self): return pi*self.radius**2 a = Square(5) b = Circle(8) print(b) print(b.fact()) print(a.fact()) print(b.area())

Смотрим на вывод программы:

Круг Я - двумерная фигура Любой угол квадрата равняется 90 градусам. 201.06192982974676

В работе кода мы использовали методы __str__() — они не были переопределены в дочерних классах и применяются непосредственно из класса-родителя. То есть интерпретатор «Пайтона» автоматически распознал, что метод fact() для объекта a (class Square) является переопределенным. В результате применяется тот метод, который был определен в классе-потомке.

В это же самое время, метод fact() для объекта b переопределенным не является, в результате чего применяется метод с таким же именем из parent class (Shape).

Полиморфизм в «Пайтон»

Важно отметить, что в «Питоне» не поддерживается такой вариант method overriding, как создание методов с тем же самым именем, однако с различными типами аргументов.

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

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

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