Ответ 1
Чтобы понять, что происходит, вы должны понять, как оператор in
, критерий членства, ведет себя для разных типы.
Для списков это довольно просто из-за того, что в основном состоит из списков: упорядоченные массивы, которые не заботятся о дубликатах. Единственный возможный способ определить критерий членства здесь - перебрать список и проверить каждый элемент на равенстве. Что-то вроде этого:
# x in lst
for item in lst:
if x == item:
return True
return False
Словари немного разные: они хэш-таблицы были ключами, которые должны быть уникальными. Для таблиц хэш требуется, чтобы ключи были хешируемыми, что по существу означает, что должна быть явная функция, которая преобразует объект в целое. Это хеш-значение затем используется для размещения ссылки/значения где-то в хеш-таблице.
Так как хэш-значение определяет, где в хеш-таблице помещается элемент, его критическое значение для объектов, которые должны быть одинаковыми, выдает одно и то же значение хэш-функции. Таким образом, следующая импликация должна быть верной: 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))
Обратите внимание, что вы не должны делать хешируемый объект, если он изменен:
Если класс определяет изменчивые объекты и реализует метод
__eq__()
, он не должен реализовывать__hash__()
, так как для реализации коллекций хеширования требуется, чтобы хэш-значение ключей было неизменным (если значение хеша объектов изменяется, оно будет в неправильном ковше хэша).