__lt__ вместо __cmp__ - PullRequest
94 голосов
/ 30 июня 2009

В 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__?

Я понимаю, что операторы <, <=, == и т. Д. Могут быть перегружены для других целей и могут возвращать любой объект, который им нравится. Я не спрашиваю о достоинствах этого подхода, а только о различиях при использовании этих операторов для сравнений в том же смысле, что и для чисел.

Обновление: Как указал Кристофер , cmp исчезает в 3.x. Существуют ли какие-либо альтернативы, облегчающие реализацию сравнений, как указано выше __cmp__?

Ответы [ 5 ]

85 голосов
/ 30 июня 2009

Да, легко реализовать все с точки зрения, например, __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__), он должен определять их напрямую, чтобы не использовать версии миксина (например, в случае * 1012). *) - на самом деле __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__ адресов, которые нужны напрямую.

44 голосов
/ 25 апреля 2012

Для упрощения этого случая в 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()))
9 голосов
/ 30 июня 2009

Это покрывается PEP 207 - Богатые сравнения

Кроме того, __cmp__ исчезает в Python 3.0. (Обратите внимание, что он отсутствует на http://docs.python.org/3.0/reference/datamodel.html, но на http://docs.python.org/2.7/reference/datamodel.html)

0 голосов
/ 30 августа 2012

Вдохновленный ответами ComparableMixin & KeyedMixin Алекса Мартелли, я придумал следующий миксин. Это позволяет реализовать один метод _compare_to(), который использует сравнения на основе ключей аналогично KeyedMixin, но позволяет вашему классу выбрать наиболее эффективный ключ сравнения на основе типа other. (Обратите внимание, что этот миксин мало помогает для объектов, которые можно проверить на равенство, но не порядок).

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
0 голосов
/ 22 августа 2011

(отредактировано 17.06.17 для учета комментариев.)

Я опробовал аналогичный миксиновый ответ выше. Я столкнулся с проблемой "Нет". Вот модифицированная версия, которая обрабатывает сравнения на равенство с «Нет». (Я не видел причин для беспокойства сравнениями неравенства с 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    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...