2012-04-20 5 views
23

docsは、__hash__メソッドと__eq__メソッドを定義する限り、クラスはハッシュ可能であると言います。しかし:Xは非ハッシュになり何ユーザー定義クラスをハッシュできないものにするには?

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' 

同じ値にハッシュするのは同じリスト(通常の等価関係)を持つ必要があります。そうでなければ、私は、ハッシュ関数にviolate this requirement意志:

唯一の必須プロパティが等しい比較オブジェクトが持っていることであるが

は、ドキュメントがハッシュ可能オブジェクトが変更されるべきではないことを警告しない同一のハッシュ値その生涯の間に、もちろん私は作成後にXのインスタンスを変更しません。もちろん、通訳はそれをチェックしません。

+2

うん、読み取り専用のインターフェイスが同じであるが、なぜあなたはそれだけの独自の外部インタフェースを使用するタプル.__ hash__を期待していますクラス?特にC言語で書かれている場合は、外部インタフェースを使用する方がはるかに遅くなります。クラスBがクラスAからサブクラス化されていない限り、クラスAのメソッドがクラスBのために機能することは、合理的に期待できません。x1 .__ hash __()を呼び出そうとしましたか? –

+0

@LennartRegebroはい、私は同意します... http://stackoverflow.com/a/10254636/336527の私の最後のコメントを参照してください...私はちょうど脳の凍結していた。 – max

答えて

15

単に__hash__メソッドをtupleクラスのメソッドに設定するだけでは不十分です。あなたは実際に違う方法でハッシュする方法を実際には言いませんでした。タプルは不変なのでハッシュ可能です。あなたが実際にあなたのカスタムリストのサブクラスをハッシュする方法を定義している。この場合

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

:あなたが本当にあなたの具体的な例の仕事作りたいと思った場合、それはこのようなことかもしれません。ハッシュを生成する方法を正確に定義するだけです。

def __hash__(self): 
    return hash("foobar"*len(self)) 
+0

しかし、 'tuple .__ hash__'はタプルをとり、数値を返す関数ではありませんか?この関数は、私のオブジェクトが実際には 'tuple'ではなく' list'であることを「認識」します - 二つの型の読み込みAPIは同じです。 – max

+0

@max: 'tuple .__ hash__'は、タプルクラスのバインドされたメソッドです。あなたはそのメソッド内で何が実行されていてもハッシュを変更していません。あなた自身を定義してください。 – jdi

+0

'hash((1,2,3))'は '(1,2,3).__ hash__'と同じです。これは 'tuple .__ hash __((1,2,3))'と同じですが、そうですか?したがって、 'tuple .__ hash__'は、' tuple'クラスの非公開APIに依存します。したがって、 'tuple'のパブリックAPIにマッチする別のクラスのインスタンスを渡すと混乱するエラーメッセージで壊れますか?私はそれを説明すると思うが、予期せぬものだ。 – max

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)) 
+0

私は非常に残念ですが、この質問は別のものの文脈から取り出されています。私はちょうどこの特定の行動について混乱していました。私がサブクラスリストを作る理由はちょっと複雑です([この質問へのコメント](http://stackoverflow.com/questions/10253783/making-a-list-subclass-hashable)の議論を参照)。 – max

+0

ActiveState Python 3.2では、このコードは私にとっては機能しません。最近の行動はおそらく変化したのでしょうか? – max

+0

私はPython 2.6を使用しています。いずれにしても、 'id'をキーとして使うのは良い考えではないので、この振る舞いは望ましくありません。タプルとハッシュに変換するほうが良い。そして実際には - すみません。これは私の問題に対するかなり難解なアプローチでした。 – senderle

5

あなたがしてやる必要がある可能性がどのような、あなたの他の質問に基づいて、次のとおりです。 はちょうどタプルをカプセル化し、何かをサブクラス化していません。 initでそうするのはまったく問題ありません。python3のドキュメントから

>>> s 
set([()]) 
>>> x1 
() 
+0

そうですが、多くのユースケースで最もクリーンでシンプルなソリューションです。 +1 – senderle

4

をクラスが、それはどちらか __hashの__()操作を定義するべきではありません__eqの__()メソッドを定義していない場合は得

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} 

; __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())) 
+0

'__hash__'は一意である必要がありますか? 'HashableAgain'のインスタンスが' __eq__'で定義した基準に基づいて比較されるようにしたいとしたら '__hash__'で定数を返すことができますか? (私は本当にハッシュを理解していない)オブジェクトのメンバーシップを決定する際に使用されます。 –

+0

@MinhTran:一般的に、ユニークではなく、_relatively_ユニークなハッシュです。これは、マップ内のバケット値に使用されます。ハッシュに一定の値を使用すると、すべての値が同じバケットに表示されるため、パフォーマンスは恐ろしいものになりますが、それでも機能するはずです! – kevinarpe

関連する問題