Полиморфизм и утиная типизация в Python
Полиморфизм — когда разные классы отвечают на один и тот же вызов каждый по-своему, а ваш код не интересуется, чей метод выполнится. Разбираем один интерфейс, утиную типизацию и функции над разнородными объектами.
Редакция Питоники
В уроке про super() и MRO было понятно, как Python находит метод. Полиморфизм — вопрос другого уровня: он позволяет разным классам отвечать на один вызов по-своему, а вашему коду — не интересоваться, чей именно метод выполнится. Здесь один интерфейс, утиная типизация и функции над разнородными объектами; в конце покажем, где гибкости становится слишком много и нужен контракт из урока про ABC.
Один интерфейс — много реализаций
Полиморфизм — свойство кода работать с объектами разных классов через одинаковые вызовы. Список уведомлений: письмо, SMS, push — каналы разные, но у каждого объекта есть метод send(). Цикл не проверяет, кто перед ним: он просто вызывает метод и верит, что каждый класс справится.
class EmailNotifier:
def send(self, text):
return f"письмо: {text}"
class SmsNotifier:
def send(self, text):
return f"смс: {text}"
class PushNotifier:
def send(self, text):
return f"push: {text}"
notifiers = [EmailNotifier(), SmsNotifier(), PushNotifier()]
for notifier in notifiers:
print(notifier.send("встреча в 18:00"))
письмо: встреча в 18:00 смс: встреча в 18:00 push: встреча в 18:00
Цикл не импортирует классы и не знает их имён: важен только метод send(). Добавите четвёртый канал — TelegramNotifier — и цикл не изменится ни на символ. Именно так строят плагины и драйверы: система зависит от интерфейса, а не от перечня классов.
class Cart:
def __init__(self, items):
self.items = items
def total(self):
return sum(self.items)
class Order:
def __init__(self, amount, fee):
self.amount = amount
self.fee = fee
def total(self):
return self.amount + self.fee
def print_total(payable):
print("к оплате:", payable.total())
print_total(Cart([120, 80, 45]))
print_total(Order(500, 30))
к оплате: 245 к оплате: 530
Утиная типизация: важны методы, а не предки
В Java или C# полиморфизм требует общего предка или интерфейса. В Python общего родителя не нужно: объект годится, если у него есть нужный метод: предки не важны — в этом и есть утиная типизация. Название — из шутки: «если это выглядит как утка, плавает как утка и крякает как утка — это утка». Родословную никто не спрашивает. Общий родитель — способ добавить гарантию (об этом в следующем уроке), но сам механизм полиморфизма работает и без него. Проверить наличие метода можно функцией hasattr.
class Thermometer:
def __init__(self, celsius):
self.celsius = celsius
def reading(self):
return f"{self.celsius}°C"
class FahrenheitThermometer:
def __init__(self, fahrenheit):
self.fahrenheit = fahrenheit
def reading(self):
return f"{self.fahrenheit}°F"
def show(sensor):
if hasattr(sensor, "reading"):
print("показание:", sensor.reading())
else:
print("это не датчик")
show(Thermometer(23))
show(FahrenheitThermometer(73))
show("строка")
показание: 23°C показание: 73°F это не датчик
| Подход | Что требуется | Когда выбирать |
|---|---|---|
| Общий родитель | Базовый класс с общим кодом | Классы реально делят состояние и поведение |
| Утиная типизация | Совпадающие имена методов | Объекты разнородны, общего кода нет |
| ABC и @abstractmethod | Базовый класс с контрактом | Нужна гарантия: без реализации методов объект не создать |
Сначала предскажи ответ в голове — это главный навык программиста.
class Cat:
def sound(self):
return "мяу"
class Dog:
def sound(self):
return "гав"
sounds = [animal.sound() for animal in [Cat(), Dog()]]
print(" ".join(sounds))
class Ruble:
def amount(self):
return 100
def double(thing):
return thing.amount() * 2
print(double(Ruble()))
class Box:
def __init__(self):
self.label = "коробка"
print(hasattr(Box(), "open"), hasattr("строка", "upper"))
1. Что такое полиморфизм в Python?
2. Что важно при утиной типизации?
3. Что произойдёт, если у объекта нет вызываемого метода?
4. Почему цикл по списку уведомлений не ломается при добавлении нового класса?
5. Чем опасны цепочки if isinstance(...) в полиморфном коде?
Создайте классы CardPayment и CashPayment с полем amount и методом pay(): первый возвращает строку «карта: сумма руб. оплачены», второй — «наличные: сумма руб. оплачены». Соберите список из CardPayment(300) и CashPayment(700) и в цикле выведите результат pay() для каждого — цикл не должен знать классы.
Что такое полиморфизм в Python простыми словами?
Это когда один и тот же вызов работает по-разному для разных объектов: payable.total() посчитает сумму корзины у Cart и сумму с комиссией у Order. Код пишется против метода, а не против класса, поэтому новые типы подхватываются без правок.
Урок 8: super() и MROЧто такое утиная типизация и чем она опасна?
Утиная типизация — подход, при котором важны методы объекта, а не его класс: нет общего предка и формальных интерфейсов. Опасность одна: если у объекта нет нужного метода, ошибка AttributeError вылезет только в момент вызова. Там, где нужна проверка заранее, используют абстрактные классы ABC.
Урок 10: ABC и @abstractmethodНужен ли общий родитель для полиморфизма в Python?
Нет: механизм полиморфизма работает через имена методов и не смотрит на иерархию. Общий родитель добавляет не полиморфизм, а общий код и общий тип; абстрактный родитель — гарантию, что потомки реализуют обязательные методы.
Как проверить, что у объекта есть нужный метод?
Функцией hasattr(obj, "имя") — вернёт True или False. Идиоматичнее стиль EAFP: просто вызвать метод и перехватить AttributeError, если объекта-метода нет. Проверки типов через isinstance в полиморфном коде считаются антипаттерном.
Понравился урок? Сошлитесь на него
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
ООП · Урок 8
super() и порядок MRO: как Python ищет методы
super() вызывает не «метод родителя», а следующий метод в порядке MRO — порядке, в котором Python ищет атрибуты по иерархии. Разбираем __mro__, расширение методов и кооперативные цепочки вызовов.
ООП · Урок 10
Абстрактные классы: ABC и @abstractmethod
Абстрактный класс объявляет контракт: какие методы обязан иметь каждый потомок, — и запрещает создавать объекты, пока контракт не выполнен. Разбираем ABC, @abstractmethod и шаблонный метод.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
NumPy · Урок 1
Что такое NumPy и как установить через pip: первый массив ndarray
Первый массив ndarray: создаём, сравниваем со списком, разбираем dtype и shape — и ускоряем сумму миллиона чисел примерно в сто раз.
что такое numpy простыми словами
NumPy · Урок 5
Broadcasting в NumPy: как складывать массивы разных размеров
Складываем вектор с матрицей без циклов: три правила broadcasting, столбцы через reshape и np.newaxis и честный разбор ошибки operands could not be broadcast together.
что такое broadcasting в numpy простыми словами
ООП · Урок 5
Инкапсуляция: _protected и __private
Одно подчёркивание — договорённость, два — искажение имени. Учимся прятать детали класса за публичным интерфейсом и понимать name mangling.
инкапсуляция простыми словамиинтерфейс класса python