Перейти к содержанию

232 уроков, 16 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 9 из 20 Средний 30 мин 120 XP

Полиморфизм и утиная типизация в 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.

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"))
Проверь себя
0 / 5

1. Что такое полиморфизм в Python?

2. Что важно при утиной типизации?

3. Что произойдёт, если у объекта нет вызываемого метода?

4. Почему цикл по списку уведомлений не ломается при добавлении нового класса?

5. Чем опасны цепочки if isinstance(...) в полиморфном коде?

Карточки терминов
Запомнено: 0 / 6
Практика

Создайте классы CardPayment и CashPayment с полем amount и методом pay(): первый возвращает строку «карта: сумма руб. оплачены», второй — «наличные: сумма руб. оплачены». Соберите список из CardPayment(300) и CashPayment(700) и в цикле выведите результат pay() для каждого — цикл не должен знать классы.

practice.py
Вопросы и ответы по уроку

Что такое полиморфизм в 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-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.