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

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

class T1:
     n=10
     def total(self, N):
          self.total = int(self.n) + int(N)

class T2:
     def total(self,s):
          self.total = len(str(s))

t1 = T1()
t2 = T2()
t1.total(45)
t2.total(45)
print(t1.total) # Вывод: 55
print(t2.total) # Вывод: 2

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

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

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

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

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

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

>>> class A:
...     def __init__(self, v1, v2):
...             self.field1 = v1
...             self.field2 = v2
...
>>> a = A(3, 4)
>>> print(a)
<__main__.A object at 0x7f840c8acfd0>

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

class A:
    def __init__(self, v1, v2):
        self.field1 = v1
        self.field2 = v2
    def __str__(self):
        return str(self.field1) + " " + str(self.field2)

a = A(3, 4)
print(a)

Вывод:

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): # количество строк
            rect.append(self.s * self.w) # знак повторяется w раз
        rect = '\n'.join(rect) # превращаем список в строку
        return rect

b = Rectangle(10, 3, '*')
print(b)

Вывод:

**********
**********
**********

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

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

Также в Python существует правосторонний метод перегрузки сложения - __radd__().

Следуя полиморфизму ООП, метод __add__() может возвращать любой результат. Например, его выполнение может быть направлено на молчаливое изменение сущности объекта без явного возврата значений. В вашей программе метод перегрузки сложения может возвращать новый экземпляр того же класса.

Вопросы для самопроверки:

  1. Что такое полиморфизм в объектно-ориентированном программировании?
  2. Как меняется реализация метода в зависимости от класса, которому он принадлежит?
  3. Возможно ли применение полиморфизма без использования наследования? Приведите пример.
  4. Как метод __str__() влияет на вывод информации объектом класса?
  5. Что такое метод перегрузки оператора сложения в Python и как он реализован?