Определение операторов
Почти любой оператор Python можно определить и для типов данных, которые мы сами создаем с помощью классов. Это делается с помощью специальных методов. О них и пойдет речь в этом уроке.
Специальные методы
На предыдущем занятии мы обсудили полиморфизм на примере оператора +
. Оператор +
работает для многих встроенных типов данных: чисел, строк, списков, кортежей. Однако возможность определять операторы есть не только у встроенных типов данных.
Специальные методы имеют для интерпретатора особое значение. Имена специальных методов и их смысл определены создателями языка: создавать новые нельзя, можно только реализовывать существующие. Названия всех специальных методов начинаются и заканчиваются на два подчеркивания.
Пример такого метода — уже знакомый нам __init__
. Он предназначен для инициализации экземпляров и автоматически вызывается интерпретатором после создания экземпляра объекта.
Остальные специальные методы также вызываются в строго определенных ситуациях. Большинство из них отвечает за реализацию операторов. Так, например, всякий раз, когда интерпретатор встречает запись вида x + y
, он заменяет ее на x.__add__(y)
, и для реализации сложения нам достаточно определить в классе экземпляра x
метод __add__
.
Обратите внимание: в методе __add__
мы создаем новый экземпляр с результатом сложения, а не изменяем уже существующий. Для арифметических операторов мы будем поступать так почти всегда, ведь при выполнении z = x + y
ни x
, ни y
изменяться не должны. Должен создаваться новый объект z
с результатом операции.
Кстати, именно поэтому в некоторых случаях запись a = a + b
отличается от a += b
. В первом случае вызывается метод __add__
, а во втором — __iadd__
, для чисел эти методы работают одинаково, а для списков нет.
Так как объекты класса Time относятся к изменяемым, при вызове __iadd__
должен изменяться сам объект. Давайте добавим этот метод в наш класс и посмотрим, как он работает.
Обратите внимание: мы не только изменяем атрибуты объекта в методе __iadd__
, но и после всех преобразований возвращаем self
— сам объект (если мы ничего не вернем, в переменной будет None
).
Другой специальный метод позволяет избавиться от вызовов метода info
перед передачей данных в print
.
Метод str
Перед выводом аргументов на печать функция print
преобразует их в строки с помощью функции str
. Но функция str
делает это не сама, а вызывает метод __str__
своего аргумента. Так что вызов str(x)
эквивалентен x.__str__()
.
Если мы сейчас попытаемся распечатать экземпляры Time
просто с помощью print(t1)
, получим что-то вроде:
Это сработала реализация метода __str__
по умолчанию из класса object
. Дело в том, что при создании класса можно указать так называемый суперкласс, от которого наш класс получит всю функциональность. Такой процесс называется наследованием. Об этом механизме мы поговорим на следующем уроке.
Если суперкласс не указать, по умолчанию наследуется класс object
, содержащий некоторую базовую функциональность, в том числе метод __str__
.
Если мы определим в своем классе собственный метод __str__
, он заменит тот, что был унаследован от object
.
Метод repr
Давайте проведем эксперимент: создадим несколько объектов типа Time
, положим их в список, а затем попытаемся вывести его на печать.
Но почему такое произошло? Мы же добавили метод для преобразования к строке! Оказывается, кроме метода __str__
, который предназначен для выдачи информации об экземпляре для пользователей в «человеческом» виде, часто определяется метод __repr__
. Для метода __repr__
, как и для __len__
, есть функция, явно вызывающая этот метод у объекта.
Функция repr
предназначена для выдачи полной информации об объекте для программиста. Она часто применяется при отладке. Поскольку «сырой» вывод списка обычно не предназначен для пользователя, он вызывает у объектов не метод __str__
, а метод __repr__
. Для нашего класса Time
этот метод мог бы выглядеть так:
Как видно, здесь метод __repr__
выдает строку, которую можно скопировать и вставить в исходный код на Python, чтобы получить выражение, которое заново сконструирует такой же объект.
Класс «Вектор на плоскости»
Двумерные векторы — очень полезный и важный геометрический объект. Векторы любой нужной размерности уже есть в библиотеке Numpy
, но, если бы мы захотели реализовать двумерный вектор самостоятельно, можно было бы сделать это, например, так:
В этом примере определены методы __add__
и __sub__
для реализации классических операций сложения и вычитания векторов. Метод __mul__
реализует операцию умножения вектора на число, а метод __rmul__
— операцию умножения числа на вектор. Для преобразования в строку используется уже знакомый нам метод __str__
.
Другие специальные методы
Специальных методов слишком много, чтобы рассмотреть их все на этом уроке. Мы приведем лишь небольшой их список.
Метод | Описание |
---|---|
| Сложение (x + y). Будет вызвано: x.__add__(y) |
| Вычитание (x - y) |
| Умножение (x * y) |
| Деление (x / y) |
| Сложение (x + y). Будет вызвано: x.__add__(y) |
| Вычитание (x - y) |
| Умножение (x * y) |
| Деление (x / y) |
| Целочисленное деление (x // y) |
| Остаток от деления (x % y) |
| Частное и остаток (divmod(x, y)) |
| Сложение (y + x). Будет вызвано: y.__radd__(x) |
| Вычитание (y - x) |
| Сравнение (x < y). Будет вызвано: x.__lt__(y) |
| Сравнение (x == y). Будет вызвано: x.__eq__(y) |
| Возвращение длины объекта |
| Доступ по индексу (или ключу) |
| Вызов экземпляра класса как функции |
Однако найти полную документацию по специальным методам в Интернете сравнительно легко. Если вам нужно реализовать тот или иной оператор, для начала поищите соответствующий ему специальный метод на втором листе вот этой шпаркалки
Если вы не нашли необходимой информации, рекомендуем очень подробную статью с длинным и обстоятельным описанием
Ну и конечно же, никто не должен забывать про официальную документацию на сайте