Как переопределить операторы сравнения объектов с использованием `__eq__`, `__ne__`, `__lt__` и других?

Для переопределения операторов сравнения в Python, таких как `==`, `!=`, `<`, `>`, `<=`, `>=`, используются специальные методы:
  • __eq__(self, other): Определяет поведение оператора `==` (равно).
  • __ne__(self, other): Определяет поведение оператора `!=` (не равно).
  • __lt__(self, other): Определяет поведение оператора `<` (меньше).
  • __gt__(self, other): Определяет поведение оператора `>` (больше).
  • __le__(self, other): Определяет поведение оператора `<=` (меньше или равно).
  • __ge__(self, other): Определяет поведение оператора `>=` (больше или равно).
Эти методы должны возвращать `True` или `False` в зависимости от результата сравнения. Пример:

class MyClass:
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        if isinstance(other, MyClass):
            return self.value == other.value
        return False

    def __lt__(self, other):
        if isinstance(other, MyClass):
            return self.value < other.value
        return NotImplemented
  
Важно: Если определить только `__eq__`, рекомендуется также определить `__ne__`. Если определить один из методов `__lt__`, `__gt__`, `__le__`, `__ge__`, рассмотрите возможность использования декоратора `functools.total_ordering`, чтобы автоматически сгенерировать остальные.

Переопределение операторов сравнения в Python позволяет нам определить, как объекты нашего класса должны сравниваться друг с другом с использованием стандартных операторов: == (равно), != (не равно), < (меньше), > (больше), <= (меньше или равно), и >= (больше или равно).

Для этого используются специальные методы (magic methods или dunder methods), имена которых соответствуют операторам:

  • __eq__(self, other): Реализует оператор == (равно). Должен возвращать True, если self и other равны, и False в противном случае.
  • __ne__(self, other): Реализует оператор != (не равно). Должен возвращать True, если self и other не равны, и False в противном случае. Часто (но не всегда) реализация может быть просто возвращением not (self == other).
  • __lt__(self, other): Реализует оператор < (меньше). Должен возвращать True, если self меньше other, и False в противном случае.
  • __gt__(self, other): Реализует оператор > (больше). Должен возвращать True, если self больше other, и False в противном случае.
  • __le__(self, other): Реализует оператор <= (меньше или равно). Должен возвращать True, если self меньше или равен other, и False в противном случае.
  • __ge__(self, other): Реализует оператор >= (больше или равно). Должен возвращать True, если self больше или равен other, и False в противном случае.

Пример:


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        if isinstance(other, Point):
            return self.x == other.x and self.y == other.y
        return False # Сравнение с объектом другого типа

    def __ne__(self, other):
        return not self.__eq__(other)

    def __lt__(self, other):
        if isinstance(other, Point):
            return (self.x**2 + self.y**2) < (other.x**2 + other.y**2)
        return NotImplemented # Сигнализируем, что сравнение с объектом другого типа не поддерживается

    def __gt__(self, other):
        if isinstance(other, Point):
            return (self.x**2 + self.y**2) > (other.x**2 + other.y**2)
        return NotImplemented

    def __le__(self, other):
        return self.__lt__(other) or self.__eq__(other)

    def __ge__(self, other):
        return self.__gt__(other) or self.__eq__(other)

#Примеры использования
point1 = Point(1, 2)
point2 = Point(1, 2)
point3 = Point(3, 4)

print(point1 == point2)   # Вывод: True
print(point1 != point3)   # Вывод: True
print(point1 < point3)    # Вывод: True
print(point1 > point3)    # Вывод: False
print(point1 <= point2)  # Вывод: True
print(point1 >= point3)  # Вывод: False

print(point1 == "string") # Вывод: False
print(point1 < "string")  # Вывод: TypeError: '<' not supported between instances of 'Point' and 'str'

Важные моменты:

  • Тип возвращаемого значения: Все эти методы должны возвращать булево значение (True или False), за исключением случаев, когда сравнение не имеет смысла (например, сравнение объекта с объектом другого, несовместимого типа). В таких случаях можно (и рекомендуется) возвращать NotImplemented. Это позволяет Python попытаться выполнить сравнение, вызвав соответствующий метод у другого объекта (other). Если ни один из объектов не реализует корректное сравнение, будет вызвано исключение TypeError.
  • Рефлексивность и транзитивность: При проектировании логики сравнения важно обеспечивать рефлексивность (x == x должно быть всегда True), транзитивность (если x == y и y == z, то x == z), и согласованность (результат сравнения не должен меняться со временем, если значения сравниваемых объектов не меняются).
  • Наследование: Если в подклассе не переопределены методы сравнения, он унаследует их от родительского класса.
  • functools.total_ordering: Декоратор @functools.total_ordering (из модуля functools) может упростить определение всех операторов сравнения. Достаточно определить __eq__ и один из __lt__, __le__, __gt__, __ge__, а декоратор сгенерирует остальные на основе этих двух. Однако, если вам нужна более специфическая или оптимизированная реализация каждого оператора, лучше переопределить их все вручную.
0