На этом занятии
мы будем рассматривать магические методы для реализации операторов сравнения:
- __eq__() – для
равенства ==
- __ne__() – для
неравенства !=
- __lt__() – для
оператора меньше <
- __le__() – для
оператора меньше или равно <=
- __gt__() – для
оператора больше >
- __ge__() – для
оператора больше или равно >=
Рассматривать
работу этих методов мы будем на примере нашего класса Clock, который
использовали на предыдущем занятии:
class Clock:
__DAY = 86400 # число секунд в одном дне
def __init__(self, seconds: int):
if not isinstance(seconds, int):
raise TypeError("Секунды должны быть целым числом")
self.seconds = seconds % self.__DAY
def get_time(self):
s = self.seconds % 60 # секунды
m = (self.seconds // 60) % 60 # минуты
h = (self.seconds // 3600) % 24 # часы
return f"{self.__get_formatted(h)}:{self.__get_formatted(m)}:{self.__get_formatted(s)}"
@classmethod
def __get_formatted(cls, x):
return str(x).rjust(2, "0")
Изначально для
класса реализован только один метод сравнения на равенство, например:
c1 = Clock(1000)
c2 = Clock(1000)
print(c1 == c2)
Но здесь объекты
сравниваются по их id (адресу в памяти), а мы бы хотели, чтобы сравнивались
секунды в каждом из объектов c1 и c2. Для этого
переопределим магический метод __eq__(), следующим образом:
def __eq__(self, other):
if not isinstance(other, (int, Clock)):
raise TypeError("Операнд справа должен иметь тип int или Clock")
sc = other if isinstance(other, int) else other.seconds
return self.seconds == sc
Теперь, после
запуска программы видим значение True, т.к. объекты содержат одинаковое
время. Кроме того, мы можем совершенно спокойно выполнять проверку и на
неравенство:
Смотрите, если
интерпретатор языка Python не находит определение метода ==, то он
пытается выполнить противоположное сравнение с последующей инверсией
результата. То есть, в данном случае находится оператор == и выполняется
инверсия:
not
(a == b)
Давайте в этом
убедимся, поставим точку останова в метод __eq__ и запустим
программу. Как видите, он срабатывает и результат в последствии меняется на
противоположный.
Отлично, на
равенство и неравенство мы теперь можем сравнивать объекты класса Clock, а также с
целыми числами. Однако, сравнение на больше или меньше пока не работает.
Строчка программы:
приведет к
ошибке. Добавим эту операцию сравнения:
def __lt__(self, other):
if not isinstance(other, (int, Clock)):
raise TypeError("Операнд справа должен иметь тип int или Clock")
sc = other if isinstance(other, int) else other.seconds
return self.seconds < sc
Как видите, у
нас здесь получается дублирование кода. Поэтому, я вынесу общее для методов
сравнения в отдельный метода класса:
@classmethod
def __verify_data(cls, other):
if not isinstance(other, (int, Clock)):
raise TypeError("Операнд справа должен иметь тип int или Clock")
return other if isinstance(other, int) else other.seconds
А сами методы
примут вид:
def __eq__(self, other):
sc = self.__verify_data(other)
return self.seconds == sc
def __lt__(self, other):
sc = self.__verify_data(other)
return self.seconds < sc
Итак, мы
определили сравнение на равенство и меньше. Теперь, можно сравнивать объекты
класса Clock на эти операции
и дополнительно на неравенство и больше. Сейчас команда:
c1 = Clock(1000)
c2 = Clock(2000)
print(c1 < c2)
Выдаст True, так как первое
время меньше второго. И также мы можем совершенно спокойно делать проверку на
больше:
Здесь сработает
тот же метод меньше, но для объекта c2:
c2 < c1
То есть, в
отличие от оператора ==, где применяется инверсия, здесь меняется порядок
операндов. Разумеется, если в классе определен метод больше:
def __gt__(self, other):
sc = self.__verify_data(other)
return self.seconds > sc
то он будет
найден и выполнен. Подмена происходит только в случае отсутствия соответствующего
магического метода.
И то же самое для
методов сравнения на меньше или равно и больше или равно:
def __le__(self, other):
sc = self.__verify_data(other)
return self.seconds <= sc
Если мы его
вызовем непосредственно для объектов класса:
то он сработает
и результат отобразится в консоли. Но, если пропишем обратное сравнение:
то просто
изменится порядок операндов и будет взято все то же сравнение меньше или равно.
То есть, для
определения операций сравнения достаточно в классе определить только три
метода: ==, <, <=, если остальные являются их симметричной
противоположностью. В этом случае язык Python сам подберет
нужный метод и выполнит его при сравнении объектов.
Я, думаю, теперь
вы хорошо себе представляете, как можно реализовывать операции сравнения для
объектов класса.