53

ドキュメントには、__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作成後のインスタンスは変更しません。もちろん、インタプリタはとにかくそれをチェックしません。

4

5 に答える 5

37

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

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

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

def __hash__(self):
    return hash("foobar"*len(self))
于 2012-04-20T23:04:37.833 に答える
21

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()))
于 2015-04-03T14:29:58.483 に答える
6

他の質問に基づいて、できることとすべきことは、何もサブクラス化せず、タプルをカプセル化することです。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
()
于 2012-04-20T23:33:33.890 に答える
3

作成後にインスタンスを変更しない場合、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 内部がとメソッドをX持っていることを検出しているが、それらを呼び出していないことを収集します。__hash____eq__

これらすべての教訓は、実際のハッシュ関数を書くだけです。これはシーケンス オブジェクトなので、タプルに変換してハッシュ化するのが最も明白な方法です。

def __hash__(self):
    return hash(tuple(self))
于 2012-04-20T23:12:33.150 に答える
3

上記の回答への追加-python3.7+のデータクラスの特定のケースについて-データクラスをハッシュ可能にするために、使用できます

@dataclass(frozen=True)
class YourClass:
    pass

の代わりに装飾として

@dataclass
class YourClass:
    pass
于 2021-03-04T16:26:17.880 に答える