Как набор Python ([]) проверяет, равны ли два объекта? Какие методы необходимо определить объекту, чтобы это настроить?

85

Мне нужно создать объект или класс «контейнер» в Python, который будет вести учет других объектов, которые я также определяю. Одно из требований этого контейнера заключается в том, что если два объекта считаются идентичными, один (любой из них) удаляется. Моей первой мыслью было использовать в set([])качестве содержащего объекта, чтобы выполнить это требование.

Однако набор не удаляет один из двух идентичных экземпляров объекта. Что я должен определить, чтобы создать его?

Вот код Python.

class Item(object):
  def __init__(self, foo, bar):
    self.foo = foo
    self.bar = bar
  def __repr__(self):
    return "Item(%s, %s)" % (self.foo, self.bar)
  def __eq__(self, other):
    if isinstance(other, Item):
      return ((self.foo == other.foo) and (self.bar == other.bar))
    else:
      return False
  def __ne__(self, other):
    return (not self.__eq__(other))

Переводчик

>>> set([Item(1,2), Item(1,2)])
set([Item(1, 2), Item(1, 2)])

Ясно, что __eq__(), который вызывается x == y, не является методом, вызываемым набором. Что вызвано? Какой еще метод я должен определить?

Примечание. ItemS должен оставаться изменяемым и может изменяться, поэтому я не могу предоставить __hash__()метод. Если это единственный способ сделать это, я перепишу его для использования неизменяемых Items.

Ада
источник
1
Была такая же проблема. Я предполагаю, что вы манипулируете небольшими объемами данных внутри своего кода. Вероятно, это не лучший кандидат для использования базы данных. Я помню, как смог создать набор и определить функцию компаратора в C ++, и я также верю в Java, однако не похоже, что вы можете сделать это с объектами словаря в Python. Похоже, что кто-то написал на Python "наборную" библиотеку, которая может это сделать, но я не знаю об этом.
Крис Датроу

Ответы:

32

Боюсь, вам придется предоставить __hash__()метод. Но вы можете закодировать его так, чтобы он не зависел от изменяемых атрибутов вашего Item.

Eumiro
источник
3
< docs.python.org/reference/… > Во втором абзаце здесь указывается, что __hash__()следует определять только для неизменяемых объектов.
Ада,
1
@Nathanael: если объект может измениться, вы можете сделать неизменяемую копию объекта, например, frozenset () и set ().
Ли Райан,
2
@Nathanael - как бы вы хотели позвонить __eq__? Сравнивая эти (1,2) атрибуты? Затем вы должны также вернуть некоторый хэш (1,2) в свой __hash__метод.
eumiro
Или, поскольку foo и bar неизменяемы, __hash __ () может возвращать сумму хэшей foo и bar? Нет ... sum слишком ненадежно ... если бы foo было 1, а bar 2, это равнялось бы bar как 1 с foo как 2, что неверно. Какую математическую функцию можно использовать для этой цели? Модуло или деление должны работать.
Ада,
1
Нафанаил: Просто используйте hashвстроенную команду: hash((self.foo, self.bar)). При этом используется хэш кортежа, который подходит для ваших нужд. (Ваше __eq__также можно было бы написать в терминах tupleсравнения.)
Пи Делпорт
76

Да, вам нужен __hash__()-метод И оператор сравнения, который вы уже предоставили.

class Item(object):
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar
    def __repr__(self):
        return "Item(%s, %s)" % (self.foo, self.bar)
    def __eq__(self, other):
        if isinstance(other, Item):
            return ((self.foo == other.foo) and (self.bar == other.bar))
        else:
            return False
    def __ne__(self, other):
        return (not self.__eq__(other))
    def __hash__(self):
        return hash(self.__repr__())
руена
источник
3
В Python 3 вам не нужно __ne__, и даже в 2.x вы не должны определять __ne__в терминах __eq__. Смотрите, stackoverflow.com/a/30676267/5337834
Джон Струд