web-dev-qa-db-ja.com

ユーザー定義クラスをハッシュ化できないものは何ですか?

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のインスタンスを作成後に変更しません。もちろん、通訳はそれをチェックしません。

36
max

単に__hash__メソッドをTupleクラスのメソッドに追加するだけでは不十分です。実際には、ハッシュを別の方法でハッシュする方法を伝えていません。タプルは不変であるため、ハッシュ可能です。特定の例を実際に機能させたい場合は、次のようになります。

class X2(list):
    def __hash__(self):
        return hash(Tuple(self))

この場合、実際にはカスタムリストサブクラスをハッシュする方法を定義しています。ハッシュを生成する方法を正確に定義する必要があります。タプルのハッシュ方法を使用するのではなく、好きなものをハッシュできます:

def __hash__(self):
    return hash("foobar"*len(self))
24
jdi

Python3のドキュメントから:

クラスが__eq __()メソッドを定義しない場合、クラスは__hash __()オペレーションも定義しないでください。 __eq __()を定義していて__hash __()を定義していない場合、そのインスタンスはハッシュ可能なコレクションのアイテムとして使用できません。クラスが可変オブジェクトを定義し、__ eq __()メソッドを実装する場合、__ hash __()を実装しないでください。ハッシュ可能なコレクションの実装では、キーのハッシュ値が不変である必要があるためです(オブジェクトのハッシュ値が変更された場合、それは間違っていますハッシュバケット)。

参照: object .__ hash __(self)

サンプルコード:

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()))
12
kevinarpe

他の質問に基づいてできることとすべきことは、サブクラス化せず、タプルをカプセル化することです。 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
()
5
ch3ka

作成後に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))
3
senderle