Понятие полиморфизма

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

Ключевые аспекты полиморфизма

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

Python поддерживает полиморфизм "утиной типизации" - если объект ведет себя как утка (имеет нужные методы), значит это утка:

Базовый полиморфизм
# Базовый класс
class Животное:
    def __init__(self, имя):
        self.имя = имя
    
    def издать_звук(self):
        pass  # Абстрактный метод
    
    def описание(self):
        return f"{self.имя} - животное"

# Подклассы
class Собака(Животное):
    def издать_звук(self):
        return f"{self.имя} говорит: Гав-гав!"
    
    def описание(self):
        return f"{self.имя} - собака"

class Кошка(Животное):
    def издать_звук(self):
        return f"{self.имя} говорит: Мяу-мяу!"
    
    def описание(self):
        return f"{self.имя} - кошка"

class Птица(Животное):
    def издать_звук(self):
        return f"{self.имя} говорит: Чирик-чирик!"
    
    def описание(self):
        return f"{self.имя} - птица"

# Функция, использующая полиморфизм
def показать_животное(животное):
    print(животное.описание())
    print(животное.издать_звук())
    print()

# Использование полиморфизма
животные = [
    Собака("Бобик"),
    Кошка("Мурка"),
    Птица("Чижик")
]

for животное in животные:
    показать_животное(животное)

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

Использование абстрактных классов для обеспечения полиморфного поведения:

Абстрактные классы и полиморфизм
from abc import ABC, abstractmethod

# Абстрактный базовый класс
class Фигура(ABC):
    def __init__(self, название):
        self.название = название
    
    @abstractmethod
    def площадь(self):
        pass
    
    @abstractmethod
    def периметр(self):
        pass
    
    def описание(self):
        return f"Это {self.название}"

# Конкретные реализации
class Прямоугольник(Фигура):
    def __init__(self, ширина, высота):
        super().__init__("прямоугольник")
        self.ширина = ширина
        self.высота = высота
    
    def площадь(self):
        return self.ширина * self.высота
    
    def периметр(self):
        return 2 * (self.ширина + self.высота)

class Круг(Фигура):
    def __init__(self, радиус):
        super().__init__("круг")
        self.радиус = радиус
    
    def площадь(self):
        import math
        return math.pi * self.радиус ** 2
    
    def периметр(self):
        import math
        return 2 * math.pi * self.радиус

class Треугольник(Фигура):
    def __init__(self, сторона_a, сторона_b, сторона_c):
        super().__init__("треугольник")
        self.сторона_a = сторона_a
        self.сторона_b = сторона_b
        self.сторона_c = сторона_c
    
    def площадь(self):
        # Формула Герона
        p = self.периметр() / 2
        return (p * (p - self.сторона_a) * (p - self.сторона_b) * (p - self.сторона_c)) ** 0.5
    
    def периметр(self):
        return self.сторона_a + self.сторона_b + self.сторона_c

# Функция, работающая с любыми фигурами
def анализировать_фигуру(фигура):
    print(фигура.описание())
    print(f"Площадь: {фигура.площадь():.2f}")
    print(f"Периметр: {фигура.периметр():.2f}")
    print()

# Использование полиморфизма
фигуры = [
    Прямоугольник(5, 3),
    Круг(4),
    Треугольник(3, 4, 5)
]

for фигура in фигуры:
    анализировать_фигуру(фигура)

Полиморфизм в функциях

Функции в Python могут работать с объектами разных типов благодаря полиморфизму:

Полиморфные функции
# Функции, демонстрирующие полиморфизм
def длина_объекта(объект):
    return len(объект)

def итерировать_объект(объект):
    результат = []
    for элемент in объект:
        результат.append(элемент)
    return результат

def строковое_представление(объект):
    return str(объект)

# Разные типы объектов
объекты = [
    "строка",
    [1, 2, 3, 4],
    {"ключ": "значение"},
    (1, 2, 3),
    {1, 2, 3}
]

for объект in объекты:
    print(f"Объект: {объект}")
    print(f"Тип: {type(объект).__name__}")
    print(f"Длина: {длина_объекта(объект)}")
    print(f"Элементы: {итерировать_объект(объект)}")
    print(f"Строковое представление: {строковое_представление(объект)}")
    print()

Перегрузка операторов

Python позволяет переопределять поведение операторов для пользовательских классов:

Перегрузка операторов
class Вектор:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    # Сложение векторов
    def __add__(self, другой):
        if isinstance(другой, Вектор):
            return Вектор(self.x + другой.x, self.y + другой.y)
        return NotImplemented
    
    # Умножение вектора на число
    def __mul__(self, скаляр):
        if isinstance(скаляр, (int, float)):
            return Вектор(self.x * скаляр, self.y * скаляр)
        return NotImplemented
    
    # Умножение числа на вектор
    def __rmul__(self, скаляр):
        return self.__mul__(скаляр)
    
    # Строковое представление
    def __str__(self):
        return f"Вектор({self.x}, {self.y})"
    
    # Представление для отладки
    def __repr__(self):
        return f"Вектор({self.x}, {self.y})"
    
    # Сравнение векторов
    def __eq__(self, другой):
        if isinstance(другой, Вектор):
            return self.x == другой.x and self.y == другой.y
        return False
    
    # Длина вектора
    def __abs__(self):
        import math
        return math.sqrt(self.x ** 2 + self.y ** 2)

# Использование перегруженных операторов
вектор1 = Вектор(3, 4)
вектор2 = Вектор(1, 2)

print(f"Вектор 1: {вектор1}")
print(f"Вектор 2: {вектор2}")
print(f"Сумма: {вектор1 + вектор2}")
print(f"Умножение на 3: {вектор1 * 3}")
print(f"Умножение 2 на вектор: {2 * вектор2}")
print(f"Равны ли векторы: {вектор1 == вектор2}")
print(f"Длина вектора 1: {abs(вектор1):.2f}")

Полиморфизм в обработке исключений

Исключения в Python также демонстрируют полиморфное поведение:

Полиморфизм исключений
# Функция, которая может вызывать разные исключения
def обработать_данные(данные):
    try:
        if isinstance(данные, str):
            if not данные:
                raise ValueError("Строка не должна быть пустой")
            return len(данные)
        elif isinstance(данные, list):
            if not данные:
                raise IndexError("Список не должен быть пустым")
            return sum(данные)
        elif isinstance(данные, dict):
            if not данные:
                raise KeyError("Словарь не должен быть пустым")
            return len(данные)
        else:
            raise TypeError(f"Неподдерживаемый тип данных: {type(данные)}")
    except (ValueError, IndexError, KeyError, TypeError) as e:
        print(f"Ошибка: {e}")
        return None

# Тестирование с разными типами данных
тестовые_данные = [
    "Привет, мир!",
    "",
    [1, 2, 3, 4, 5],
    [],
    {"ключ1": "значение1", "ключ2": "значение2"},
    {},
    42
]

for данные in тестовые_данные:
    print(f"Обработка: {данные} (тип: {type(данные).__name__})")
    результат = обработать_данные(данные)
    if результат is not None:
        print(f"Результат: {результат}")
    print()

Практические примеры

Рассмотрим практические примеры использования полиморфизма:

Система обработки платежей
from abc import ABC, abstractmethod

# Абстрактный базовый класс для платежных систем
class ПлатежнаяСистема(ABC):
    def __init__(self, имя):
        self.имя = имя
    
    @abstractmethod
    def обработать_платеж(self, сумма):
        pass
    
    @abstractmethod
    def получить_комиссию(self, сумма):
        pass

# Конкретные реализации
class КредитнаяКарта(ПлатежнаяСистема):
    def __init__(self, имя, номер_карты):
        super().__init__(имя)
        self.номер_карты = номер_карты
    
    def обработать_платеж(self, сумма):
        комиссия = self.получить_комиссию(сумма)
        итоговая_сумма = сумма + комиссия
        return f"Платеж {сумма} через кредитную карту обработан. Комиссия: {комиссия}. Итого: {итоговая_сумма}"
    
    def получить_комиссию(self, сумма):
        return сумма * 0.02  # 2% комиссия

class PayPal(ПлатежнаяСистема):
    def __init__(self, имя, email):
        super().__init__(имя)
        self.email = email
    
    def обработать_платеж(self, сумма):
        комиссия = self.получить_комиссию(сумма)
        итоговая_сумма = сумма + комиссия
        return f"Платеж {сумма} через PayPal обработан. Комиссия: {комиссия}. Итого: {итоговая_сумма}"
    
    def получить_комиссию(self, сумма):
        return сумма * 0.03  # 3% комиссия

class БанковскийПеревод(ПлатежнаяСистема):
    def __init__(self, имя, номер_счета):
        super().__init__(имя)
        self.номер_счета = номер_счета
    
    def обработать_платеж(self, сумма):
        комиссия = self.получить_комиссию(сумма)
        итоговая_сумма = сумма + комиссия
        return f"Платеж {сумма} банковским переводом обработан. Комиссия: {комиссия}. Итого: {итоговая_сумма}"
    
    def получить_комиссию(self, сумма):
        return max(50, сумма * 0.01)  # Минимум 50 или 1%

# Система обработки платежей
class ПлатежныйПроцессор:
    def __init__(self):
        self.платежные_системы = []
    
    def добавить_систему(self, система):
        self.платежные_системы.append(система)
    
    def обработать_платежи(self, сумма):
        print(f"Обработка платежа на сумму {сумма} через все доступные системы:")
        for система in self.платежные_системы:
            print(система.обработать_платеж(сумма))
            print()

# Использование полиморфизма в системе платежей
процессор = ПлатежныйПроцессор()
процессор.добавить_систему(КредитнаяКарта("Visa", "****1234"))
процессор.добавить_систему(PayPal("PayPal", "user@example.com"))
процессор.добавить_систему(БанковскийПеревод("SWIFT", "SWIFT123456789"))

процессор.обработать_платежи(1000)

Практические задания

Задания
  1. Создайте систему "Медиа проигрыватель" с базовым классом и подклассами (аудио, видео, изображение), реализующими полиморфное воспроизведение.
  2. Реализуйте полиморфную систему "Геометрические тела" с методами вычисления объема и площади поверхности.
  3. Создайте полиморфную систему "Транспортные средства" с методами движения и получения информации о скорости.
  4. Разработайте полиморфную систему "Фигуры" с перегруженными операторами для сложения и сравнения фигур по площади.
Решения:

Задание 1:

from abc import ABC, abstractmethod

class Медиа(ABC):
    def __init__(self, название, длительность=0):
        self.название = название
        self.длительность = длительность
    
    @abstractmethod
    def воспроизвести(self):
        pass
    
    @abstractmethod
    def пауза(self):
        pass
    
    @abstractmethod
    def остановить(self):
        pass

class Аудио(Медиа):
    def __init__(self, название, исполнитель, длительность):
        super().__init__(название, длительность)
        self.исполнитель = исполнитель
    
    def воспроизвести(self):
        return f"Воспроизводится аудио '{self.название}' от {self.исполнитель}"
    
    def пауза(self):
        return f"Аудио '{self.название}' поставлено на паузу"
    
    def остановить(self):
        return f"Аудио '{self.название}' остановлено"

class Видео(Медиа):
    def __init__(self, название, режиссер, длительность):
        super().__init__(название, длительность)
        self.режиссер = режиссер
    
    def воспроизвести(self):
        return f"Воспроизводится видео '{self.название}' от режиссера {self.режиссер}"
    
    def пауза(self):
        return f"Видео '{self.название}' поставлено на паузу"
    
    def остановить(self):
        return f"Видео '{self.название}' остановлено"

class Изображение(Медиа):
    def __init__(self, название, художник):
        super().__init__(название)
        self.художник = художник
    
    def воспроизвести(self):
        return f"Отображается изображение '{self.название}' от художника {self.художник}"
    
    def пауза(self):
        return "Изображения не поддерживают паузу"
    
    def остановить(self):
        return f"Изображение '{self.название}' скрыто"

# Пример использования
медиа_файлы = [
    Аудио("Bohemian Rhapsody", "Queen", 354),
    Видео("Inception", "Christopher Nolan", 8880),
    Изображение("Mona Lisa", "Leonardo da Vinci")
]

for медиа in медиа_файлы:
    print(медиа.воспроизвести())
    print(медиа.пауза())
    print(медиа.остановить())
    print()

Задание 2:

from abc import ABC, abstractmethod
import math

class ГеометрическоеТело(ABC):
    def __init__(self, название):
        self.название = название
    
    @abstractmethod
    def объем(self):
        pass
    
    @abstractmethod
    def площадь_поверхности(self):
        pass

class Куб(ГеометрическоеТело):
    def __init__(self, сторона):
        super().__init__("куб")
        self.сторона = сторона
    
    def объем(self):
        return self.сторона ** 3
    
    def площадь_поверхности(self):
        return 6 * self.сторона ** 2

class Шар(ГеометрическоеТело):
    def __init__(self, радиус):
        super().__init__("шар")
        self.радиус = радиус
    
    def объем(self):
        return (4/3) * math.pi * self.радиус ** 3
    
    def площадь_поверхности(self):
        return 4 * math.pi * self.радиус ** 2

class Цилиндр(ГеометрическоеТело):
    def __init__(self, радиус, высота):
        super().__init__("цилиндр")
        self.радиус = радиус
        self.высота = высота
    
    def объем(self):
        return math.pi * self.радиус ** 2 * self.высота
    
    def площадь_поверхности(self):
        return 2 * math.pi * self.радиус * (self.радиус + self.высота)

# Функция для анализа тел
def анализировать_тело(тело):
    print(f"Анализ {тела.название}а:")
    print(f"Объем: {тело.объем():.2f}")
    print(f"Площадь поверхности: {тело.площадь_поверхности():.2f}")
    print()

# Пример использования
тела = [
    Куб(3),
    Шар(2),
    Цилиндр(2, 5)
]

for тело in тела:
    анализировать_тело(тело)

Задание 3:

from abc import ABC, abstractmethod

class ТранспортноеСредство(ABC):
    def __init__(self, марка, модель):
        self.марка = марка
        self.модель = модель
    
    @abstractmethod
    def двигаться(self):
        pass
    
    @abstractmethod
    def максимальная_скорость(self):
        pass
    
    def информация(self):
        return f"{self.марка} {self.модель}"

class Автомобиль(ТранспортноеСредство):
    def __init__(self, марка, модель, тип_двигателя):
        super().__init__(марка, модель)
        self.тип_двигателя = тип_двигателя
    
    def двигаться(self):
        return f"{self.информация()} едет по дороге"
    
    def максимальная_скорость(self):
        return 200  # км/ч

class Самолет(ТранспортноеСредство):
    def __init__(self, марка, модель, тип_крыла):
        super().__init__(марка, модель)
        self.тип_крыла = тип_крыла
    
    def двигаться(self):
        return f"{self.информация()} летит в воздухе"
    
    def максимальная_скорость(self):
        return 900  # км/ч

class Корабль(ТранспортноеСредство):
    def __init__(self, марка, модель, тип_корпуса):
        super().__init__(марка, модель)
        self.тип_корпуса = тип_корпуса
    
    def двигаться(self):
        return f"{self.информация()} плывет по воде"
    
    def максимальная_скорость(self):
        return 50  # узлов

# Функция для демонстрации полиморфизма
def демонстрация_движения(транспорт):
    print(транспорт.двигаться())
    print(f"Максимальная скорость: {транспорт.максимальная_скорость()} км/ч")
    print()

# Пример использования
транспортные_средства = [
    Автомобиль("Toyota", "Camry", "бензиновый"),
    Самолет("Boeing", "747", "крыло переменной стреловидности"),
    Корабль("Queen Mary 2", "лайнер", "стальной")
]

for транспорт in транспортные_средства:
    демонстрация_движения(транспорт)

Задание 4:

from abc import ABC, abstractmethod
import math

class Фигура(ABC):
    def __init__(self, название):
        self.название = название
    
    @abstractmethod
    def площадь(self):
        pass
    
    def __str__(self):
        return f"{self.название} (площадь: {self.площадь():.2f})"
    
    # Перегрузка оператора сложения
    def __add__(self, другая):
        if isinstance(другая, Фигура):
            общая_площадь = self.площадь() + другая.площадь()
            return СоставнаяФигура([self, другая], общая_площадь)
        return NotImplemented
    
    # Перегрузка операторов сравнения
    def __lt__(self, другая):
        if isinstance(другая, Фигура):
            return self.площадь() < другая.площадь()
        return NotImplemented
    
    def __le__(self, другая):
        if isinstance(другая, Фигура):
            return self.площадь() <= другая.площадь()
        return NotImplemented
    
    def __eq__(self, другая):
        if isinstance(другая, Фигура):
            return self.площадь() == другая.площадь()
        return NotImplemented
    
    def __ne__(self, другая):
        if isinstance(другая, Фигура):
            return self.площадь() != другая.площадь()
        return NotImplemented
    
    def __gt__(self, другая):
        if isinstance(другая, Фигура):
            return self.площадь() > другая.площадь()
        return NotImplemented
    
    def __ge__(self, другая):
        if isinstance(другая, Фигура):
            return self.площадь() >= другая.площадь()
        return NotImplemented

class Прямоугольник(Фигура):
    def __init__(self, ширина, высота):
        super().__init__("прямоугольник")
        self.ширина = ширина
        self.высота = высота
    
    def площадь(self):
        return self.ширина * self.высота

class Круг(Фигура):
    def __init__(self, радиус):
        super().__init__("круг")
        self.радиус = радиус
    
    def площадь(self):
        return math.pi * self.радиус ** 2

class СоставнаяФигура(Фигура):
    def __init__(self, фигуры, общая_площадь):
        super().__init__("составная фигура")
        self.фигуры = фигуры
        self._общая_площадь = общая_площадь
    
    def площадь(self):
        return self._общая_площадь

# Пример использования
прямоугольник = Прямоугольник(4, 5)
круг = Круг(3)

print(f"Фигура 1: {прямоугольник}")
print(f"Фигура 2: {круг}")

# Сложение фигур
составная = прямоугольник + круг
print(f"Составная фигура: {составная}")

# Сравнение фигур
print(f"Прямоугольник < Круг: {прямоугольник < круг}")
print(f"Прямоугольник > Круг: {прямоугольник > круг}")
print(f"Прямоугольник == Круг: {прямоугольник == круг}")

# Сортировка фигур по площади
фигуры = [Прямоугольник(3, 4), Круг(2), Прямоугольник(5, 2), Круг(3)]
print("\nФигуры до сортировки:")
for фигура in фигуры:
    print(фигура)

print("\nФигуры после сортировки по площади:")
фигуры.sort()
for фигура in фигуры:
    print(фигура)
Предыдущий урок Следующий урок