Все разделы
Выбрать школу

Школа. Увлечения. Большое будущее.

PROSCHOOLS · ОБРАЗОВАНИЕ РЯДОМ

Ваш город

Выберите город, чтобы находить образование и возможности рядом.

Как учитываем городКаталог и кнопки «Выбрать школу» покажут школы выбранного города.Онлайн-курсы, учебник, ГДЗ и задания к экзаменам доступны по всей России.

Популярные города

Будет выбран: Вся Россия

Учебные материалы останутся прежними.

Поиск по сайту

По всему сайтуШколы выбранного города — выше в результатах

С чего начнём?

↑ ↓ — выбрать результат · Enter — открыть · Esc — закрыть

Полиморфизм функций в Python: один интерфейс — разные объекты

💻 Информатика · 10 класс

Полиморфизм в Python держится на утиной типизации: функции важно, что объект умеет, а не от какого он класса. Разбираем общий интерфейс вызова, отличие перегрузки операторов от перегрузки функций.

Клавиатура в компьютерном классе
Иллюстрация к предмету «Информатика»Hmartil · Wikimedia Commons · CC BY-SA 4.0

Один интерфейс — разные объекты

Полиморфизм буквально означает «много форм»: одна и та же операция работает с объектами разных типов. Встроенная функция len() — готовый пример: она считает символы строки, элементы списка и пары словаря, не спрашивая заранее, что ей передали.

print(len('кот'))             # 3 — символы строки
print(len([1, 2, 3]))         # 3 — элементы списка
print(len({'a': 1, 'b': 2}))  # 2 — пары словаря

Мы не выбирали версию len() в зависимости от типа — Python сам нашёл нужное поведение у каждого объекта.

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

Официальный глоссарий Python называет такой стиль утиной типизацией: тип объекта не проверяется, нужный метод или атрибут вызывается напрямую. «Если это выглядит как утка и крякает как утка, значит, это утка». Интерфейс важнее происхождения класса: функции достаточно, чтобы у каждого переданного объекта был метод area().

Пример 1. Суммарная площадь фигур

import math

class Circle:
    def __init__(self, radius):
        if radius <= 0:
            raise ValueError('радиус должен быть положительным')
        self.radius = radius
    def area(self):
        return math.pi * self.radius ** 2

class Square:
    def __init__(self, side):
        if side <= 0:
            raise ValueError('сторона должна быть положительной')
        self.side = side
    def area(self):
        return self.side ** 2

def total_area(shapes):
    return sum(shape.area() for shape in shapes)

figures = [Circle(1), Square(2), Circle(0.5)]
print(total_area(figures))  # сумма трёх площадей

Классы Circle и Square не связаны наследованием и не знают друг о друге. Но оба отвечают на вызов area(), поэтому total_area принимает любую смесь фигур. Проверено выполнением с точностью до 1e-9, в том числе для total_area([]): сумма по пустому списку равна 0. В учебном примере размеры предполагаются конечными числами. Инициализаторы отклоняют нулевые и отрицательные размеры через ValueError: вызов класса не возвращает успешно инициализированный экземпляр.

Пример 2. Когда интерфейса нет

Утиная типизация честно сообщает о проблеме: если у объекта нет нужного метода, возникнет AttributeError.

class Stone:
    pass

try:
    total_area([Stone()])
except AttributeError:
    print('объект не умеет вычислять площадь')

Перехватывается именно AttributeError — конкретное исключение, а не всё подряд: но такой обработчик поймает и AttributeError, возникший внутри area(), например из-за опечатки в имени атрибута. Поэтому он годится здесь для демонстрации, а причину неожиданной ошибки нужно искать по traceback, не объявляя любой AttributeError отсутствием метода.

Перегрузка операторов — не перегрузка функций

Не путайте два понятия. Перегрузка операторов — когда класс задаёт поведение оператора для своих объектов, например +:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __add__(self, other):
        if not isinstance(other, Vector):
            return NotImplemented
        return Vector(self.x + other.x, self.y + other.y)

v = Vector(1, 2) + Vector(3, 4)
print(v.x, v.y)  # 4 6

Метод __add__ объясняет, что означает + для Vector; для неподдерживаемого типа он возвращает NotImplemented, позволяя Python попробовать отражённую операцию или сообщить TypeError. Обычные определения def с одним именем не создают перегрузку по числу или типам аргументов: второй def привязывает имя к новой функции. Старую функцию можно сохранить заранее под другим именем. Проверено: после повторного определения вызов со «старым» числом аргументов выбрасывает TypeError. Функцию с гибким числом аргументов собирают иначе — значениями по умолчанию или *args.

В рассмотренных примерах полиморфизм означает одну операцию, работающую с разными объектами: сложение склеивает строки и складывает числа, а смешать их ('2' + 3) нельзя — будет TypeError. Оба случая проверены выполнением. Для выбора реализации по типу первого аргумента существует functools.singledispatch; typing.overload описывает варианты вызова для статического анализатора и сам не выбирает реализацию при запуске.

Проверь себя

  1. Почему total_area работает со списком из Circle и Square? Ответ: у обоих классов есть метод area(), а тип объектов функция не проверяет.
  2. Что будет, если передать в total_area объект без метода area()? Ответ: AttributeError при попытке вызова.
  3. Нужно ли Circle и Square наследовать от общего класса, чтобы пример 1 работал? Ответ: нет — достаточно общего интерфейса вызова.
  4. Можно ли в Python объявить две функции size с разным числом аргументов? Ответ: два def написать можно, но второе определение привяжет имя к новой функции; автоматического выбора по числу аргументов не получится. Для гибкости используют значения по умолчанию или *args.
  5. Что вернёт len для объекта собственного класса? Ответ: если класс реализует __len__, len вернёт вычисленную длину — неотрицательное целое. Без поддержки длины возникнет TypeError; некорректный результат __len__ тоже вызовет исключение.

Кратко о главном

Полиморфизм — один интерфейс вызова у разных объектов. Python опирается на утиную типизацию: важно, что объект умеет, а не от какого класса произведён. Перегрузка операторов (методы вида __add__) задаёт поведение операторов для класса; повторные def не создают автоматический выбор функции по сигнатуре. Отсутствие метода — законный AttributeError, а не повод перехватывать все исключения подряд.

Источники и материалы