Полиморфизм

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

Например, два разных класса содержат метод 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)

Вывод:

<__main__.A object at 0x7f251ac2f8d0>
<__main__.A object at 0x7f251ac2f8d0>

Здесь мы используем переменную 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-версия


Объектно-ориентированное программирование на Python




Все разделы сайта