Для того, чтобы понять, что происходит, вы должны понимать, как работает оператор in
, membership test, для разных типов.
Для списков это довольно просто из-за того, что в основном состоит из списков: упорядоченные массивы, которые не заботятся о дубликатах. Единственный возможный способ определить критерий членства здесь - перебрать список и проверить каждый элемент на равенстве. Что-то вроде этого:
# x in lst
for item in lst:
if x == item:
return True
return False
Словари немного отличаются: они хэш-таблицы были ключи предназначены быть уникальным. Для таблиц хэш требуется, чтобы ключи были hashable, что по существу означает, что должна быть явная функция, которая преобразует объект в целое число. Это хеш-значение затем используется, чтобы поместить отображение ключа/значения где-то в хеш-таблицу.
Поскольку значение хеш определяет, где в хеш-таблице помещается элемент, важно, чтобы объекты, которые должны быть одинаковыми, производят одно и то же значение хэш-функции. Таким образом, следующее значение должно быть правдой: x == y => hash(x) == hash(y)
. Однако обратное не обязательно должно быть истинным; это совершенно верно, если разные объекты производят одно и то же значение хэш-функции.
Когда выполняется тест на членство в словаре, словарь сначала ищет хеш-значение. Если он найдет его, он выполнит проверку равенства всех найденных элементов; если он не нашел значение хеш-функции, то это предполагает, что это другой объект:
# x in dct
h = hash(x)
items = getItemsForHash(dct, h)
for item in items:
if x == item:
return True
# items is empty, or no match inside the loop
return False
Поскольку вы получите желаемый результат при использовании теста членства в отношении списка, это означает, что ваш объект реализует сравнение равенства (__eq__
) правильно. Но так как вы не получите правильный результат при использовании словаря, кажется, быть __hash__
реализация, которая находится вне синхронизации с реализацией сравнения равенства:
>>> class SomeType:
def __init__ (self, x):
self.x = x
def __eq__ (self, other):
return self.x == other.x
def __hash__ (self):
# bad hash implementation
return hash(id(self))
>>> l = [SomeType(1)]
>>> d = { SomeType(1): 'x' }
>>> x = SomeType(1)
>>> x in l
True
>>> x in d
False
Заметим, что для новых классов в Python 2 (классы, которые наследуют от object
), эта «неудачная хэш-реализация» (которая основана на идентификаторе объекта) является значением по умолчанию. Поэтому, когда вы не реализуете свою собственную функцию __hash__
, она по-прежнему использует ее.Это в конечном итоге означает, что если ваш __eq__
выполняет проверку подлинности (по умолчанию), хеш-функция будет не синхронизирована.
Таким образом, решение заключается в реализации __hash__
таким образом, чтобы оно соответствовало правилам, используемым в __eq__
. Например, если вы сравниваете двух членов self.x
и self.y
, то вы должны использовать сложный хэш над этими двумя членами. Самый простой способ сделать это, чтобы вернуть хэш-значение кортежа из этих значений:
class SomeType (object):
def __init__ (self, x, y):
self.x = x
self.y = y
def __eq__ (self, other):
return self.x == other.x and self.y == other.y
def __hash__ (self):
return hash((self.x, self.y))
Обратите внимание, что вы не должны делать объект hashable, если он изменчив:
Если класс определяет изменяемые объекты и реализует метод __eq__()
, он не должен реализовывать __hash__()
, так как для реализации коллекций хеширования требуется, чтобы хэш-значение ключа было неизменным (если значение хэша объекта изменяется, оно будет в неправильном хэш-ведре).
@vaultah Он должен (в противном случае вы получите unhashable TypeError), но реализация, скорее всего, не выровнен с реализацией '__eq__'. – poke
Как вы применили 'to_user' и основной класс? Словари Python не сохраняют повторяющиеся объекты, потому что у вас есть одно и то же значение '__hash__', но если вы создаете несколько экземпляров из одного класса каждый раз, вы получите новый объект с другим значением хэша. (с этой точки зрения, что они имеют одинаковое представление), но результат в вашем словаре не будет представлением, потому что они являются одними и теми же строками и, следовательно, имеют одно и то же значение хэш-функции. – Kasramvd
@poke Вы отправили отличный ответ ниже +1. Тем не менее, ваш комментарий о неконтролируемой TypeError неверен, [как показано в этом ответе] (http://stackoverflow.com/a/17445665/1431750). – aneroid