Как не прописывать действие для каждой операции сравнения?
Приветствую.
Пишу свой класс и хочу прописать для него такие методы как __lt__, __le__, __ge__, __gt__, __eq__, но не хочу писать десяток одинаковых методов, которые отличаются на один-два знака. Подскажите, как всё это сделать красиво и элегантно?
Сейчас пытаюсь что-то намудрить с лямбдой и переменной __name__ и с атрибутом __dict__, но пока результата нет.
А python разве не умеет делать типа __lt__ = lambda x: not self.__ge__(x)? Мне казалось что я читал, что можно определить, например, "<", и тогда другие операции python _сам_ определит автоматом: "a >= b" == not "a < b"; "a == b" == "a >= b" and "b >= a" и т.д. Не оптимально, конечно, но зато не требует писать десяток почти одинаковых методов.
Роман: Часть методов python действительно неявно определяет по аналогии, но не все и чтобы это применять - надо разбираться. Если вы определяете __lt__ (<), то неявно автоопределится __gt__ (>), но не __ge__ (>=) и __le__ (<=). Даже если определен __eq__ - все равно. Определение __eq__ (==), кстати, не дает вам автоматически __ne__(!=).
С другой стороны, если определены __len__ и __getitem__, то __iter__ прописывать не надо. И т д
Если вы определяете __lt__ (<), то неявно автоопределится __gt__ (>), но не __ge__ (>=) и __le__ (<=).
С чего вдруг там __gt__ определится? Если что-то не меньше, то это недостаточно для того, чтобы оно было больше.
Благо питон даёт удобный интерпретатор, в котором легко всё проверяется:
>>> class A:
... def __lt__(self, v):
... return hasattr(v, 'x')
...
>>> class B:
... x = 1
...
>>> class C:
... y = 1
...
>>> a, b, c = A(), B(), C()
>>>
>>> a < b
True
>>> a > b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: A() > B()
>>> a == b
False
>>> a != b
True
>>> a <= b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: A() <= B()
>>> a >= b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: A() >= B()
>>>
>>> a < c
False
>>> a > c
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: A() > C()
>>> a == c
False
>>> a != c
True
>>> a <= c
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: A() <= C()
>>> a >= c
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: A() >= C()
>>>
>>> class Test:
def __init__(self,value):
self._value = value
#
def __lt__(self,other):
if type(other) != Test:
raise ValueError('comparing object must be of type Test')
return self._value < other._value
... ... ... ... ... ... ... ...
>>> a = Test(10)
>>> b = Test(20)
>>> a < b
True
>>> a > b
False
>>> a < 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __lt__
ValueError: comparing object must be of type Test
>>> class Test:
... def __init__(self,value):
... self._value = value
...
... def __lt__(self,other):
... if type(other) != Test:
... raise ValueError('comparing object must be of type Test')
... return self._value < other._value
...
>>> a = Test(10)
>>> b = Test(20)
>>> a < b
True
>>> a > b
False
>>> a < 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __lt__
ValueError: comparing object must be of type Test
>>> a > 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: Test() > int()
>>>
>>> class Test:
... def __init__(self,value):
... self._value = value
...
... def __lt__(self,other):
... print('lt')
... if type(other) != Test:
... raise ValueError('comparing object must be of type Test')
... return self._value < other._value
...
>>> a = Test(10)
>>> b = Test(20)
>>> a < b
lt
True
>>> a > b
lt
False
>>>
abcd0x00: Эврика! : ) Именно это я и имел в виду, когда говорил "Если вы определяете __lt__ (<), то неявно автоопределится __gt__ (>)". Согласен, выражение немного некорректное, поскольку __gt__ все же не определяется (что несложно проверить при помощи a.__gt__(b) ). Правильнее выразиться так: "Если реализован метод __lt__, то класс будет поддерживать синтакс a < b, а также, неявно, синтакс a > b". Думаю, это всех устроит.
Суть этой фишки в том, чтобы не прописывать часть зеркальных методов при реализации взаимодействия экземпляров вашего класса друг с другом. Но опять же, чтобы это делать нужно в теме неплохо разбираться. Например, если определить __eq__, то будет корректно работать как a == b, так и a != b. А вот если определить только __ne__, то корректно будет работать само a != b, а a == b будет эквивалентно a is b.
Наследуйтесь от базового типа, где уже есть схожие операции.
И еще вопрос - вам точно нужна своя структура данных? Потому что базовые типы овероптимизированы в отличии от подделок.
Смотреть, от чего наследоватся, можно, например, по __slots__, dir или в документации.
Для сложных данных неоднозначного уровня вложенности, вроде XML, данные, по которым должно идти сравнение, можно продублировать (дату или идентификатор) и записать в значение родительского класса.
Vladimir Abiduev: да, парсить-то само собой, батарейками. Но после парсинга может получиться сложная вложенная структура. Даже не сильно вложенная, а пусть простой одномерный словарь. И вот нам надо эти данные иметь отсортированными по значению какого-то ключа, например, по дате. В таком случае тот класс, в котором мы будем хранить данные, наследуем от класса datetime.date и спокойно составляем из объектов этого класса список. Потом не менее спокойно этот список сортируем и выводим или итерируем его.