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

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

  • автор:

Полиморфизм

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

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

class T1: def __init__(self): self.n = 10 def total(self, a): return self.n + int(a) class T2: def __init__(self): self.string = 'Hi' def total(self, a): return len(self.string + str(a)) t1 = T1() t2 = T2() print(t1.total(35)) # Вывод: 45 print(t2.total(35)) # Вывод: 4

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

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

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

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

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

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

class A: def __init__(self, v1, v2): self.field1 = v1 self.field2 = v2 a = A(3, 4) b = str(a) print(a) print(b)

Здесь мы используем переменную b , чтобы показать, что функция print() вызывает str() неявным образом, так как вывод значений обоих переменных одинаков.

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

class A: def __init__(self, v1, v2): self.field1 = v1 self.field2 = v2 def __str__(self): s = str(self.field1)+" "+str(self.field2) return s a = A(3, 4) b = str(a) print(a) print(b)
3 4 3 4

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

class Rectangle: def __init__(self, width, height, sign): self.w = int(width) self.h = int(height) self.s = str(sign) def __str__(self): rect = [] # количество строк for i in range(self.h): # знак повторяется w раз rect.append(self.s * self.w) # превращаем список в строку rect = '\n'.join(rect) return rect b = Rectangle(10, 3, '*') print(b)

Практическая работа. Метод перегрузки оператора сложения

В качестве практической работы попробуйте самостоятельно перегрузить оператор сложения. Для его перегрузки используется метод __add__ . Он вызывается, когда объекты класса, имеющего данный метод, фигурируют в операции сложения, причем с левой стороны. Это значит, что в выражении a + b у объекта a должен быть метод __add__ . Объект b может быть чем угодно, но чаще всего он бывает объектом того же класса. Объект b будет автоматически передаваться в метод __add__(self, b) в качестве второго аргумента.

Отметим, в Python также есть правосторонний метод перегрузки сложения – __radd__ .

Согласно полиморфизму ООП, возвращать метод __add__ может что угодно. Может вообще ничего не возвращать, а «молча» вносить изменения в какие-то уже существующие объекты. Допустим, в вашей программе метод перегрузки сложения будет возвращать новый объект того же класса.

Курс с примерами решений практических работ:
pdf-версия

X Скрыть Наверх

Объектно-ориентированное программирование на 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) единым образом.

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

Вначале я продемонстрирую пример, где мы увидим один недостаток, который как раз исправляется с помощью полиморфизма. Предположим, у нас есть два класса 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. Также узнали, как можно определять методы, которые ведут себя подобно абстрактным с необходимостью их переопределения в дочерних классах.

Видео по теме

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

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

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

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

Введение

Буквальное значение полиморфизма — это условие возникновения различных форм.

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

Давайте рассмотрим первый пример — полиморфизм в операторе сложения.

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

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

a = 2 b = 2 print(a + b)

Аналогично, для строковых типов данных оператор + используется для выполнения конкатенации:

a = "Программировать" b = "интересно" print(a + " " + b)
Программировать интересно

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

Эти примеры являются одними из самых простых проявлений полиморфизма в Python.

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

В Python есть некоторые функции, которые совместимы для работы с несколькими типами данных.

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

Давайте рассмотрим несколько примеров использования этой функции:

print(len("Программист")) print(len(["Яблоко", "Банан", "Груша"])) print(len())
11 3 2

Здесь мы видим, что многие типы данных, такие как string, list, tuple, set и dictionary, могут работать с функцией len. Однако хочу отметить, что она возвращает конкретную информацию о конкретных типах данных.

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

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

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

Затем мы можем обобщить вызов этих методов, игнорируя объект, с которым мы работаем.

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

class Cat: def __init__(self, klichka, vozrast): self.klichka = klichka self.vozrast = vozrast def status(self): print(f"Я кошка. Меня зовут . Мой возраст лет") def say(self): print("Мяу") class Dog: def __init__(self, klichka, vozrast): self.klichka = klichka self.vozrast = vozrast def status(self): print(f"Я собака. Меня зовут . Мой возраст лет") def say(self): print("Гав") cat_obj = Cat("Муська", 10) dog_obj = Dog("Барон", 12) for pet in (cat_obj, dog_obj): pet.say() pet.status() pet.say()
Мяу Я кошка. Меня зовут Муська. Мой возраст 10 лет Мяу Гав Я собака. Меня зовут Барон. Мой возраст 12 лет Гав

Здесь мы создали два класса: Cat и Dog. Они имеют схожую структуру и одинаковые имена методов status и say.

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

Это возможно благодаря полиморфизму!

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

Как и в других языках программирования, дочерние классы в Python также наследуют методы и атрибуты от родительского класса. Мы можем переопределить некоторые методы и атрибуты специально для того, чтобы они соответствовали дочернему классу, который известен как «Переопределение методов» (Method Overriding).

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

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

from math import pi class Shape: def __init__(self, name): self.name = name def area(self): pass def info(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 info(self): return "Квадраты имеют каждый угол равный 90 градусам." class Circle(Shape): def __init__(self, radius): super().__init__("Круг") self.radius = radius def area(self): return pi * self.radius ** 2 kvadrat = Square(8) krug = Circle(14) print(kvadrat) print(kvadrat.info()) print(krug.info()) print(kvadrat.area())
Квадрат Квадраты имеют каждый угол равный 90 градусам. Я двухмерная форма. 64

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

Из-за полиморфизма интерпретатор Python автоматически распознает, что метод info для объекта kvadrat (класс Square) переопределен. Таким образом, он использует тот, который определен в дочернем классе.

С другой стороны, поскольку метод info для объекта krug не переопределяется, он используется из родительского класса Shape.

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

Заключение

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

Удачи в программировании!

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

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