辞書キーとしてのカスタムタイプのオブジェクト


185

カスタムタイプのオブジェクトをPython辞書のキーとして使用するにはどうすればよいですか(「オブジェクトID」をキーとして機能させたくない場合)。例:

class MyThing:
    def __init__(self,name,location,length):
            self.name = name
            self.location = location
            self.length = length

MyThingを、名前と場所が同じであれば同じと見なされるキーとして使用したいと思います。C#/ Javaから、equalsおよびhashcodeメソッドをオーバーライドして提供する必要があり、ハッシュコードが依存するものを変更しないことを約束しました。

これを行うには、Pythonで何をする必要がありますか?私もすべきですか?

(ここのような単純なケースでは、おそらく(name、location)タプルをキーとして配置するほうが良いでしょう-しかし、キーをオブジェクトにしたいと考えてください)


ハッシュを使用することの何が問題になっていますか?
Rafe Kettler、2011

5
おそらく、2つの異なる「オブジェクト」として別々に作成された場合でもMyThing、同じnameとを持っている場合location、辞書にインデックスを付けて同じ値を返すようにしたいからです。
サンタ

1
「おそらく、(名前、場所)タプルをキーとして配置する方がよいでしょう。ただし、キーをオブジェクトにしたいと考えてください)」つまり、NON-COMPOSITEオブジェクトですか?
eyquem

回答:


221

2つのメソッドを追加する必要があります。メモ__hash____eq__

class MyThing:
    def __init__(self,name,location,length):
        self.name = name
        self.location = location
        self.length = length

    def __hash__(self):
        return hash((self.name, self.location))

    def __eq__(self, other):
        return (self.name, self.location) == (other.name, other.location)

    def __ne__(self, other):
        # Not strictly necessary, but to avoid having both x==y and x!=y
        # True at the same time
        return not(self == other)

Python dictのドキュメントでは、キーオブジェクトに関するこれらの要件を定義しています。つまり、それらはハッシュ可能でなければなりません。


17
hash(self.name)と比べて見栄えがよく、自分でXORをself.name.__hash__()実行hash((x, y))しないようにすることができる場合。
Rosh Oxymoron、2011

5
追加の注記として、誤った結果生成する可能性があるため、x.__hash__()そのような呼び出しも間違っていることを発見しました:pastebin.com/C9fSH7eF
Rosh Oxymoron

@Rosh Oxymoron:コメントありがとうございます。書くときに私は明示的にandfor を使用して__eq__いましたが、「なぜタプルを使用しないのですか?」とにかくそれを頻繁に行うからです(もっと読みやすいと思います)。奇妙な理由で私の目は__hash__しかししかし質問に戻りませんでした。
6502

1
@ user877329:ブレンダーのデータ構造をキーとして使用しようとしていますか?一部のリポジトリから、特定のオブジェクトは変更を避けるために最初に「フリーズ」する必要があるようです(Python辞書でキーとして使用されている値ベースのオブジェクトの変更は許可されていません)
6502

1
@ kawing-chiu pythonfiddle.com/eq-method-needs-ne-method <-これはPython 2の「バグ」を示しています。Python3にはこの問題はありません。デフォルト__ne__()「修正」されています。
Bob Stein

34

Python 2.6以降の代替手段は使用するcollections.namedtuple()ことです-これは特別なメソッドを書く手間を省きます:

from collections import namedtuple
MyThingBase = namedtuple("MyThingBase", ["name", "location"])
class MyThing(MyThingBase):
    def __new__(cls, name, location, length):
        obj = MyThingBase.__new__(cls, name, location)
        obj.length = length
        return obj

a = MyThing("a", "here", 10)
b = MyThing("a", "here", 20)
c = MyThing("c", "there", 10)
a == b
# True
hash(a) == hash(b)
# True
a == c
# False

20

__hash__特別なハッシュセマンティクスが必要な場合、__cmp__または__eq__クラスをキーとして使用できるようにするためにオーバーライドします。等しいオブジェクトは、同じハッシュ値を持つ必要があります。

Pythonは__hash__整数を返すことを期待しているため、返すことBanana()はお勧めしません:)

既に述べたように、ユーザー定義のクラスは__hash__デフォルトでを呼び出しid(self)ます。

ドキュメントからいくつかの追加のヒントがあります

__hash__() 親クラスからメソッドを継承するが、 返されるハッシュ値の意味が変更され__cmp__()たり__eq__()(デフォルトのIDベースの等価ではなく、値ベースの等価の概念に切り替えるなど)したクラスは、次のように明示的にフラグを立てることができます。__hash__ = None クラス定義で設定することでハッシュ化できません。そうすることで、プログラムがハッシュ値を取得しようとしたときにクラスのインスタンスが適切なTypeErrorを発生させるだけでなく、チェック時にハッシュ不可として正しく識別されることになります isinstance(obj, collections.Hashable)__hash__()TypeErrorを明示的に発生させるクラスを定義するクラスとは異なります )。


2
単独のハッシュは、あなたはさらに、十分にオーバーライドするのいずれかが必要ではありません__eq____cmp__
Oben Sonne、2011

@Oben Sonne:__cmp__ユーザー定義クラスの場合、Pythonによって提供されますが、おそらく、新しいセマンティクスに対応するために、それらをオーバーライドする必要があります。
Skurmedel、2011

1
@Skurmedel:はい。ただし、これらのメソッドをオーバーライドしないユーザークラスを呼び出しcmpて使用すること=はできますが、名前と場所が類似しているインスタンスが同じ辞書キーを持っているという質問者の要件を満たすために、そのうちの1つを実装する必要があります。
Oben Sonne、2011
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.