В этой лекции мы научимся сравнивать объекты пользовательских классов между собой. По умолчанию экземпляры классов в Python не поддерживают операции сравнения. Если попробовать сравнить два объекта через операторы ==
, !=
, >
, <
, >=
, или <=
, Python выбросит исключение.
Пример ошибки при попытке сравнения двух объектов класса Shape
с использованием оператора >
:
TypeError: '>' not supported between instances of 'Shape' and 'Shape'
Причина ошибки в том, что для выполнения операций сравнения в классе должны быть определены специальные магические методы. Для каждого оператора существует свой метод, как показано в таблице ниже:
Оператор | Магический метод |
---|---|
== | __eq__ |
!= | __ne__ |
< | __lt__ |
<= | __le__ |
> | __gt__ |
>= | __ge__ |
Когда вы сравниваете объекты через A == B
, Python на самом деле вызывает A.__eq__(B)
.
Чтобы класс поддерживал сравнение, достаточно реализовать соответствующие методы. Рассмотрим пример, в котором мы создадим класс Shape
и добавим в него методы для проверки на равенство ==
и на меньшее <
.
class Shape:
def __init__(self, area):
self.area = area
def __eq__(self, other):
if isinstance(other, Shape):
return self.area == other.area
return False
def __lt__(self, other):
if isinstance(other, Shape):
return self.area < other.area
return False
Теперь объекты класса Shape
могут сравниваться друг с другом с помощью операторов ==
и <
. Например:
s1 = Shape(30)
s2 = Shape(45)
assert s1 == Shape(30) # True
assert s1 < s2 # True
assert s1 == s2 # False
Также можно реализовать логику, чтобы объекты сравнивались не только с другими объектами класса, но и с числами:
class Shape:
def __init__(self, area):
self.area = area
def __lt__(self, other):
if isinstance(other, Shape):
return self.area < other.area
elif isinstance(other, (int, float)):
return self.area < other
return NotImplemented
s = Shape(25)
assert s < 30 # True
assert s < 10 # False
Но если попытаться сравнить объект с числом через >
, возникнет ошибка:
TypeError: '>' not supported between instances of 'Shape' and 'int'
Операции сравнения являются бинарными, то есть в них участвуют два операнда. Python вызывает метод сравнения у левого операнда. Например, при выражении A > B
Python вызывает метод A.__gt__(B)
. Если метод __gt__
не определен в классе A
, Python попробует переставить операнды и изменить оператор на противоположный, вызвав метод B.__lt__(A)
.
Таким образом, если в классе Shape
метод __gt__
не реализован, а метод __lt__
есть, то при сравнении двух объектов s1 > s2
Python фактически выполнит операцию s2.__lt__(s1)
.
Однако, если мы пытаемся сравнить объект с числом через >
, Python попытается вызвать other.__lt__(self)
. Но типы int
и float
не умеют сравниваться с пользовательскими объектами, поэтому возникнет ошибка:
s > 5 # TypeError: '>' not supported between instances of 'Shape' and 'int'
Для каждого из шести операторов сравнения в Python есть соответствующий магический метод. Тем не менее, реализация всех этих методов может привести к избыточности. Чтобы этого избежать, достаточно реализовать только три метода, поскольку они противоположны друг другу:
Метод сравнения | Противоположный метод |
---|---|
__eq__ | __ne__ |
__lt__ | __gt__ |
__le__ | __ge__ |
Для примера реализуем методы __eq__
, __lt__
и __le__
. Оператор <=
можно выразить через комбинацию операторов <
и ==
, так как <=
буквально означает «меньше или равно».
class Shape:
def __init__(self, area):
self.area = area
def __eq__(self, other):
if isinstance(other, Shape):
return self.area == other.area
return False
def __lt__(self, other):
if isinstance(other, Shape):
return self.area < other.area
elif isinstance(other, (int, float)):
return self.area < other
return NotImplemented
def __le__(self, other):
return self == other or self < other
Теперь наш класс поддерживает все необходимые операторы сравнения:
s1 = Shape(30)
s2 = Shape(45)
assert s1 == Shape(30) # True
assert s1 != s2 # True
assert s1 < s2 # True
assert s1 <= s2 # True
assert s2 > s1 # True
assert s2 >= Shape(45) # True
assert s1 <= 35 # True
assert s2 > 40 # True
В этом уроке мы научились добавлять поддержку операций сравнения в пользовательские классы, используя магические методы. Мы увидели, как эффективно реализовать три из шести операторов сравнения и избежать дублирования кода.