Полиморфизм

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

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

Полиморфизм — это способность объектов разных классов реагировать на одинаковые методы или операции по-разному. В переводе с греческого "полиморфизм" означает "много форм".

Простыми словами: один и тот же метод, разное поведение, в зависимости от объекта.

Пример из жизни

Представьте кнопку "Воспроизвести" ▶️ на различных устройствах:

  • На музыкальном плеере она воспроизводит аудио 🎵
  • На видеоплеере — запускает видео 🎬
  • В игровой консоли — запускает игру 🎮

Та же кнопка (общий интерфейс), но разное поведение в зависимости от устройства — это и есть полиморфизм.

Типы полиморфизма в Python

В Python полиморфизм бывает нескольких видов:

  1. Полиморфизм с функциями и методами — одна функция работает с разными типами данных
  2. Полиморфизм с наследованием — подклассы переопределяют методы родительского класса
  3. Полиморфизм через "утиную типизацию" — если объект умеет делать то, что нам нужно, не важно, какого он типа
  4. Полиморфизм операторов — операторы (+, -, * и т.д.) работают по-разному с разными типами

Разберем каждый тип на простых примерах!

Полиморфизм с функциями и методами

Многие встроенные функции Python уже являются полиморфными — они работают одинаково с разными типами данных:

Python 3.13
# Функция len() работает с разными типами данных
>>> print(f"Длина строки: {len('Hello')}")  # Считает символы
Длина строки: 5
>>> print(f"Длина списка: {len([1, 2, 3, 4])}") # Считает элементы
Длина списка: 4
>>> print(f"Длина словаря: {len({'a': 1, 'b': 2})}") # Считает пары ключ-значение
Длина словаря: 2
# Оператор + тоже полиморфный! >>> print(f"10 + 5 = {10 + 5}") # Сложение чисел
10 + 5 = 15
>>> print(f"'Hello' + ' World' = {'Hello' + ' World'}") # Соединение строк
'Hello' + ' World' = Hello World
>>> print(f"[1, 2] + [3, 4] = {[1, 2] + [3, 4]}") # Объединение списков
[1, 2] + [3, 4] = [1, 2, 3, 4]

Видите? Функция len() и оператор + работают совершенно по-разному в зависимости от типа данных, но используются одинаково!

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

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

Python 3.13
>>> class Animal:
...     def __init__(self, name):
...         self.name = name

>>>     def speak(self):
...         # Базовый метод, который будет переопределен в подклассах
...         raise NotImplementedError("Каждое животное должно определить свой звук!")

>>>     def introduce(self):
...         # Этот метод использует speak(), но не знает, как именно он реализован
...         return f"Меня зовут {self.name}, и я говорю {self.speak()}"

>>> class Dog(Animal):
...     def speak(self):
...         return "гав!"  # Собаки лают

>>> class Cat(Animal):
...     def speak(self):
...         return "мяу!"  # Кошки мяукают

# Создаем животных и вызываем один и тот же метод
>>> dog = Dog("Бобик")
>>> cat = Cat("Мурка")

>>> print(dog.introduce())
Меня зовут Бобик, и я говорю гав!
>>> print(cat.introduce())
Меня зовут Мурка, и я говорю мяу!
# Полиморфизм в действии - одинаковая обработка разных объектов # Мы можем работать со списком животных, не зная, какие они! >>> animals = [Dog("Рекс"), Cat("Барсик"), Dog("Шарик")] >>> print("Животные представляются:")
Животные представляются:
>>> for animal in animals: ... print(f"- {animal.introduce()}")
- Меня зовут Рекс, и я говорю гав!
- Меня зовут Барсик, и я говорю мяу!
- Меня зовут Шарик, и я говорю гав!

Что здесь происходит:

  1. У нас есть базовый класс Animal с методом speak(), который должен быть переопределен
  2. Классы Dog и Cat наследуются от Animal и переопределяют метод speak()
  3. Метод introduce() определен в базовом классе и использует полиморфизм: он вызывает speak(), не зная его конкретной реализации
  4. Мы можем работать с разными животными через единый интерфейс, не беспокоясь о конкретном типе

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

Абстрактные классы помогают нам определить, какие методы должны иметь все подклассы. Они как чертеж или контракт:

Python 3.13
>>> from abc import ABC, abstractmethod  # ABC = Abstract Base Class

>>> class Shape(ABC):  # Абстрактный класс для геометрических фигур
...     @abstractmethod  # Этот декоратор означает "метод должен быть реализован в подклассах"
...     def area(self):
...         pass  # Здесь нет реализации, но она должна быть у всех подклассов

>>>     @abstractmethod
...     def perimeter(self):
...         pass

>>>     def describe(self):
...         # Этот метод использует area() и perimeter(), не зная их реализации
...         return f"Площадь: {self.area():.2f}, Периметр: {self.perimeter():.2f}"

>>> class Rectangle(Shape):
...     def __init__(self, width, height):
...         self.width = width
...         self.height = height

>>>     def area(self):
...         return self.width * self.height  # Площадь прямоугольника

>>>     def perimeter(self):
...         return 2 * (self.width + self.height)  # Периметр прямоугольника

>>> class Circle(Shape):
...     def __init__(self, radius):
...         self.radius = radius

>>>     def area(self):
...         import math
...         return math.pi * self.radius ** 2  # Площадь круга

>>>     def perimeter(self):
...         import math
...         return 2 * math.pi * self.radius  # Длина окружности

# Работаем с разными фигурами одинаково
>>> shapes = [Rectangle(5, 3), Circle(4)]
>>> for i, shape in enumerate(shapes, 1):
...     print(f"Фигура {i}: {shape.describe()}")
Фигура 1: Площадь: 15.00, Периметр: 16.00
Фигура 2: Площадь: 50.27, Периметр: 25.13

Польза абстрактных классов:

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

"Утиная типизация"

В Python есть интересный принцип: "Если нечто ходит как утка и крякает как утка, то это вероятно утка". Это означает, что важны не типы объектов, а то, что они умеют делать:

Python 3.13
>>> class Duck:
...     def swim(self):
...         return "Утка плавает"

>>>     def sound(self):
...         return "Кря-кря!"

>>> class Person:
...     def swim(self):
...         return "Человек плывет"

>>>     def sound(self):
...         return "Привет!"

# Эта функция работает с ЛЮБЫМ объектом, у которого есть методы swim() и sound()
# Не важно какого он класса!
>>> def make_it_swim_and_sound(entity):
...     print(f"Плавание: {entity.swim()}")
...     print(f"Звук: {entity.sound()}")

# Утка и человек - совершенно разные классы, но функция работает с обоими
>>> duck = Duck()
>>> person = Person()

>>> print("Утка:")
Утка:
>>> make_it_swim_and_sound(duck)
Плавание: Утка плавает
Звук: Кря-кря!
>>> print("\nЧеловек:")
Человек:
>>> make_it_swim_and_sound(person)
Плавание: Человек плывет
Звук: Привет!

В этом примере:

  • У нас нет общего базового класса
  • Duck и Person - совершенно разные классы
  • Но оба могут "плавать" и "издавать звук"
  • Наша функция работает с любым объектом, который имеет методы swim() и sound()

Это и есть "утиная типизация" - более гибкая форма полиморфизма, присущая Python.

Проверка понимания

Что из перечисленного ниже является примером полиморфизма в Python?

Преимущества полиморфизма

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

  1. Упрощение кода — пишете один метод вместо нескольких похожих
  2. Гибкость — легко добавлять новые типы, не меняя существующий код
  3. Читаемость — код становится понятнее и проще для сопровождения
  4. Возможность расширения — новые классы работают со старым кодом без изменений
  5. Меньше повторений — общая логика написана только один раз

Мы с вами на связи
Русский