docs は、クラスが__hash__
メソッドと__eq__
メソッドを定義している限り、ハッシュ可能であると言います。しかしながら:
class X(list):
# read-only interface of `Tuple` and `list` should be the same, so reuse Tuple.__hash__
__hash__ = Tuple.__hash__
x1 = X()
s = {x1} # TypeError: unhashable type: 'X'
X
をハッシュ化できないものは何ですか?
同じ値にハッシュするには、(通常の等価性に関して)同一のリストが必要であることに注意してください。それ以外の場合は、ハッシュ関数で この要件に違反 します。
唯一必要なプロパティは、同等に比較するオブジェクトが同じハッシュ値を持つことです
ドキュメントは、ハッシュ可能なオブジェクトがその存続期間中に変更されるべきではないことを警告しています。そしてもちろん、私はX
のインスタンスを作成後に変更しません。もちろん、通訳はそれをチェックしません。
単に__hash__
メソッドをTuple
クラスのメソッドに追加するだけでは不十分です。実際には、ハッシュを別の方法でハッシュする方法を伝えていません。タプルは不変であるため、ハッシュ可能です。特定の例を実際に機能させたい場合は、次のようになります。
class X2(list):
def __hash__(self):
return hash(Tuple(self))
この場合、実際にはカスタムリストサブクラスをハッシュする方法を定義しています。ハッシュを生成する方法を正確に定義する必要があります。タプルのハッシュ方法を使用するのではなく、好きなものをハッシュできます:
def __hash__(self):
return hash("foobar"*len(self))
Python3のドキュメントから:
クラスが__eq __()メソッドを定義しない場合、クラスは__hash __()オペレーションも定義しないでください。 __eq __()を定義していて__hash __()を定義していない場合、そのインスタンスはハッシュ可能なコレクションのアイテムとして使用できません。クラスが可変オブジェクトを定義し、__ eq __()メソッドを実装する場合、__ hash __()を実装しないでください。ハッシュ可能なコレクションの実装では、キーのハッシュ値が不変である必要があるためです(オブジェクトのハッシュ値が変更された場合、それは間違っていますハッシュバケット)。
サンプルコード:
class Hashable:
pass
class Unhashable:
def __eq__(self, other):
return (self == other)
class HashableAgain:
def __eq__(self, other):
return (self == other)
def __hash__(self):
return id(self)
def main():
# OK
print(hash(Hashable()))
# Throws: TypeError("unhashable type: 'X'",)
print(hash(Unhashable()))
# OK
print(hash(HashableAgain()))
他の質問に基づいてできることとすべきことは、サブクラス化せず、タプルをカプセル化することです。 initでこれを行うのはまったく問題ありません。
class X(object):
def __init__(self, *args):
self.tpl = args
def __hash__(self):
return hash(self.tpl)
def __eq__(self, other):
return self.tpl == other
def __repr__(self):
return repr(self.tpl)
x1 = X()
s = {x1}
これにより、
>>> s
set([()])
>>> x1
()
作成後にX
のインスタンスを変更しない場合、タプルをサブクラス化しないのはなぜですか?
しかし、少なくともPython 2.6では、これが実際にエラーをスローしないことを指摘しておきます。
>>> class X(list):
... __hash__ = Tuple.__hash__
... __eq__ = Tuple.__eq__
...
>>> x = X()
>>> s = set((x,))
>>> s
set([[]])
これはあなたが思っていることをしないので、私は「うまくいく」と言うのをためらいます。
>>> a = X()
>>> b = X((5,))
>>> hash(a)
4299954584
>>> hash(b)
4299954672
>>> id(a)
4299954584
>>> id(b)
4299954672
オブジェクトIDをハッシュとして使用しているだけです。実際に__hash__
を呼び出しても、エラーが発生します。 __eq__
も同様です。
>>> a.__hash__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: descriptor '__hash__' for 'Tuple' objects doesn't apply to 'X' object
>>> X().__eq__(X())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: descriptor '__eq__' for 'Tuple' objects doesn't apply to 'X' object
python internalsは、何らかの理由で、X
に__hash__
と__eq__
メソッドがあることを検出していますが、呼び出していません。それら。
これらすべての教訓は、実際のハッシュ関数を記述するだけです。これはシーケンスオブジェクトであるため、タプルに変換し、最も明白な方法であるハッシュ化を行います。
def __hash__(self):
return hash(Tuple(self))