Единственное обязательное свойство заключается в том, что сравниваемые объекты имеют одинаковое значение хеш-функции.
"Сравнение равно" в тексте спецификации означает результат их __eq__
методов -не требуется, чтобы они были одним и тем же объектом.
Предполагается, что __hash__
должны основываться на значениях, которые используются в __eq__
, а не на "id" объекта - эта часть неверна в вашем коде. Чтобы это работало, вот как это должно быть:
Просто сделайте:
...
def __eq__( self, b ):
return self.v[0] == b.v[0] and self.v[1] == b.v[1]
def __hash__( self ):
return hash((self.v[0], self.v[1]))
Означает ли это, что два разных, но эквивалентных объекта не могут использоваться в качестве разных ключей? в диктовке или выступать индивидуально в наборе?
Да. Это то, что означает спецификация.
Обходной путь для этого состоит в том, чтобы оставить реализацию __eq__
по умолчанию для вашего класса, чтобы соответствовать правилам, и внедрить альтернативную форму сравнения, которую вы должны будете использовать в своем коде.
Самый простой способ - просто оставить реализацию по умолчанию __eq__
как есть, которая сравнивается по тождеству, и иметь произвольный метод, который вы используете для сравнения (идиома, кодирующая в языках, которые делаютне поддерживает переопределение оператора должны использовать в любом случае):
class A( object ):
...
def equals( self, b ):
return self.v[0] == b.v[0] and self.v[1] == b.v[1]
p = A( 1, 0 )
q = A( 1, 0 )
print( str( p ), str( q ) )
print( "identical?", p is q )
print( "equivalent?", p.equals(q) )
Есть способы немного улучшить это - но базовый уровень: __eq__
должны соответствовать правилам и проводить сравнение личности.
Одним из способов является создание внутреннего связанного объекта, который работает как «пространство имен», которое вы можете использовать для сравнения:
class CompareSpace:
def __init__(self, parent):
self.parent = parent
def __eq__( self, other ):
other = other.parent if isinstance(other, type(self)) else other
return self.parent.v[0] == other.v[0] and other.v[1] == b.parent.v[1]
class A:
def __init__( self, v1, v2 ):
self.v = ( v1, v2 )
self.comp = CompareSpace(self)
def __str__( self ):
return str( self.v )
p = A( 1, 0 )
q = A( 1, 0 )
print( str( p ), str( q ) )
print( "identical?", p is q )
print( "equivalent?", p.comp == q )
print( "hashes", hash(p), hash(q) )
демонстрация поломки
Теперь я будувставьте пример того, как это ломается - я создаю класс намеренно более сломанным, чтобы проблема возникла с первой попытки. Но если проблема возникает хотя бы один раз каждые 2 миллиона раз, ваш код все равно будет слишком взломан, чтобы использовать его в чем-либо реальном, даже если это личный код: у вас будет недетерминированный словарь:
class Broken:
def __init__(self, name):
self.name = name
def __hash__(self):
return id(self) % 256
def __eq__(self, other):
return True
def __repr__(self):
return self.name
In [23]: objs = [Broken(f"{i:02d}") for i in range(64)]
In [24]: print(objs)
[00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63]
In [25]: test = {}
In [26]: for obj in objs:
...: if obj not in test:
...: test[obj] = 0
...:
In [27]: print(test)
{00: 0, 01: 0, 02: 0, 11: 0}
# Or with simple, unconditional, insertion:
In [29]: test = {obj: i for i, obj in enumerate(objs)}
In [30]: test
Out[30]: {00: 57, 01: 62, 02: 63, 11: 60}
(Я повторяю, в то время как ваши значения has не будут конфликтовать сами по себе, внутренний код dict должен уменьшить число в хеш-индексе до индекса в своей хеш-таблице - необязательно по модулю (%) - в противном случае для каждого пустого dict потребуется 2 ** 64 пустых записи, и только если все хеши были только 64-битными)