__lt__ вместо __cmp__
Python 2.x имеет два способа перегрузить операторы сравнения: __cmp__
или "богатые операторы сравнения", такие как __lt__
. Говорят, что предпочтительными являются богатые сравнительные перегрузки, но почему это так?
Богатые операторы сравнения проще реализовать каждый, но вы должны реализовать несколько из них с почти одинаковой логикой. Однако, если вы можете использовать встроенный порядок cmp
и кортеж, то __cmp__
становится довольно простым и выполняет все сравнения:
class A(object):
def __init__(self, name, age, other):
self.name = name
self.age = age
self.other = other
def __cmp__(self, other):
assert isinstance(other, A) # assumption for this example
return cmp((self.name, self.age, self.other),
(other.name, other.age, other.other))
Эта простота, похоже, удовлетворяет мои потребности намного лучше, чем перегрузка всех 6 (!) богатых сравнений. (Тем не менее, вы можете получить его до "просто" 4, если вы полагаетесь на "измененный аргумент" /отраженное поведение, но это приводит к чистому увеличению сложности, по моему скромному мнению.)
Есть ли непредвиденные ошибки, о которых мне нужно знать, если я только перегружаю __cmp__
?
Я понимаю, что операторы <
, <=
, ==
и т.д. могут быть перегружены для других целей и могут возвращать любой объект, который им нравится. Я не спрашиваю о достоинствах этого подхода, но только о различиях при использовании этих операторов для сравнений в том же смысле, что они означают для чисел.
Обновление: Как указано Christopher , cmp
исчезает в 3.x. Существуют ли какие-либо альтернативы, которые делают сравнение реализации так же просто, как выше __cmp__
?
Ответы
Ответ 1
Да, легко реализовать все с точки зрения, например, __lt__
с классом mixin (или метаклассом или декоратором класса, если ваш вкус работает именно так).
Например:
class ComparableMixin:
def __eq__(self, other):
return not self<other and not other<self
def __ne__(self, other):
return self<other or other<self
def __gt__(self, other):
return other<self
def __ge__(self, other):
return not self<other
def __le__(self, other):
return not other<self
Теперь ваш класс может определить только __lt__
и умножить наследование из ComparableMixin (после любых других баз, которые ему нужны, если они есть). Декоратор класса был бы очень похож, просто вставляя подобные функции в качестве атрибутов нового класса, который он украшает (результат может быть микроскопически быстрее во время выполнения с одинаково малой стоимостью с точки зрения памяти).
Конечно, если ваш класс имеет особенно быстрый способ реализации (например) __eq__
и __ne__
, он должен определять их напрямую, чтобы версии mixin не использовались (например, это относится к dict
) - на самом деле __ne__
вполне можно определить, чтобы облегчить это как:
def __ne__(self, other):
return not self == other
но в коде выше я хотел сохранить симпатичную симметрию только с помощью <
;-).
Что касается того, почему __cmp__
должен был уйти, так как у нас были __lt__
и друзья, зачем хранить другой, другой способ делать то же самое? Это так много мертвого веса в каждой среде исполнения Python (Classic, Jython, IronPython, PyPy,...). Код, в котором определенно не будет иметь ошибок, - это код, которого нет - откуда принцип Python должен быть идеальным одним очевидным способом выполнения задачи (C имеет тот же принцип в Раздел "Дух C" стандарта ISO, кстати).
Это не значит, что мы избегаем нашего способа запретить вещи (например, почти эквивалентность между миксинами и декораторами классов для некоторых целей), но это определенно означает означает, что мы не как переносить код в компиляторы и/или среды выполнения, которые избыточно существуют только для поддержки нескольких эквивалентных подходов для выполнения точно такой же задачи.
Дальнейшее редактирование: на самом деле есть еще лучший способ обеспечить сравнение и хэширование для многих классов, в том числе в вопросе - метод __key__
, как я упомянул в своем комментарии к вопросу. Поскольку я никогда не собирался писать PEP для этого, вы должны в настоящее время реализовать его с помощью Mixin (& c), если вам это нравится:
class KeyedMixin:
def __lt__(self, other):
return self.__key__() < other.__key__()
# and so on for other comparators, as above, plus:
def __hash__(self):
return hash(self.__key__())
Это очень распространенный случай сравнения экземпляров с другими экземплярами с целью сравнения кортежа для каждого с несколькими полями, а затем хеширование должно выполняться на той же основе. Специальные запросы __key__
, которые нужны непосредственно.
Ответ 2
Чтобы упростить этот случай, есть декоратор класса в Python 2.7 +/3.2 +, functools.total_ordering, который можно использовать для реализации того, что Алекс предлагает. Пример из документов:
@total_ordering
class Student:
def __eq__(self, other):
return ((self.lastname.lower(), self.firstname.lower()) ==
(other.lastname.lower(), other.firstname.lower()))
def __lt__(self, other):
return ((self.lastname.lower(), self.firstname.lower()) <
(other.lastname.lower(), other.firstname.lower()))
Ответ 3
Это покрывается PEP 207 - Богатые сравнения
Кроме того, __cmp__
исчезает в python 3.0. (Обратите внимание, что его нет в http://docs.python.org/3.0/reference/datamodel.html, но он находится на http://docs.python.org/2.7/reference/datamodel.html)
Ответ 4
(Отредактировано 6/17/17, чтобы принимать во внимание комментарии.)
Я опробовал сопоставимый ответ mixin выше. Я столкнулся с проблемой "Нет". Ниже приведена модифицированная версия, которая обрабатывает сравнение сравнений с "Нет". (Я не видел причин беспокоиться о сравнении неравенства с None как отсутствующей семантикой):
class ComparableMixin(object):
def __eq__(self, other):
if not isinstance(other, type(self)):
return NotImplemented
else:
return not self<other and not other<self
def __ne__(self, other):
return not __eq__(self, other)
def __gt__(self, other):
if not isinstance(other, type(self)):
return NotImplemented
else:
return other<self
def __ge__(self, other):
if not isinstance(other, type(self)):
return NotImplemented
else:
return not self<other
def __le__(self, other):
if not isinstance(other, type(self)):
return NotImplemented
else:
return not other<self
Ответ 5
Вдохновленный ответами Alex Martelli ComparableMixin
и KeyedMixin
, я придумал следующий микс.
Он позволяет реализовать один метод _compare_to()
, который использует сопоставления на основе ключевых слов
аналогично KeyedMixin
, но позволяет вашему классу выбрать наиболее эффективный ключ сравнения на основе типа other
. (Обратите внимание, что этот mixin не очень помогает для объектов, которые могут быть проверены на равенство, но не на порядок).
class ComparableMixin(object):
"""mixin which implements rich comparison operators in terms of a single _compare_to() helper"""
def _compare_to(self, other):
"""return keys to compare self to other.
if self and other are comparable, this function
should return ``(self key, other key)``.
if they aren't, it should return ``None`` instead.
"""
raise NotImplementedError("_compare_to() must be implemented by subclass")
def __eq__(self, other):
keys = self._compare_to(other)
return keys[0] == keys[1] if keys else NotImplemented
def __ne__(self, other):
return not self == other
def __lt__(self, other):
keys = self._compare_to(other)
return keys[0] < keys[1] if keys else NotImplemented
def __le__(self, other):
keys = self._compare_to(other)
return keys[0] <= keys[1] if keys else NotImplemented
def __gt__(self, other):
keys = self._compare_to(other)
return keys[0] > keys[1] if keys else NotImplemented
def __ge__(self, other):
keys = self._compare_to(other)
return keys[0] >= keys[1] if keys else NotImplemented