John Millikinは、次のようなソリューションを提案しました。
class A(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
def __eq__(self, othr):
return (isinstance(othr, type(self))
and (self._a, self._b, self._c) ==
(othr._a, othr._b, othr._c))
def __hash__(self):
return hash((self._a, self._b, self._c))
このソリューションの問題は、 hash(A(a, b, c)) == hash((a, b, c))
。言い換えると、ハッシュはその主要なメンバーのタプルのハッシュと衝突します。多分これは実際にはあまり重要ではありませんか?
更新:Pythonドキュメントでは、上記の例のようにタプルを使用することを推奨しています。ドキュメントには、
唯一必要なプロパティは、同等に比較するオブジェクトが同じハッシュ値を持つことです
反対は当てはまらないことに注意してください。等しいと比較されないオブジェクトは、同じハッシュ値を持つ場合があります。このようなハッシュの衝突は、オブジェクトが同等に比較されない限り、dictキーまたはセット要素として使用されたときに、あるオブジェクトが別のオブジェクトを置き換えることはありません。。
古い/悪いソリューション
に関するPythonのドキュメントで__hash__
は、XORのようなものを使用してサブコンポーネントのハッシュを組み合わせることが提案されています。
class B(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
def __eq__(self, othr):
if isinstance(othr, type(self)):
return ((self._a, self._b, self._c) ==
(othr._a, othr._b, othr._c))
return NotImplemented
def __hash__(self):
return (hash(self._a) ^ hash(self._b) ^ hash(self._c) ^
hash((self._a, self._b, self._c)))
更新:Blckknghtが指摘しているように、a、b、cの順序を変更すると問題が発生する可能性があります。^ hash((self._a, self._b, self._c))
ハッシュされる値の順序を取得するために、追加しました。このファイナル^ hash(...)
は、結合される値を再配置できない場合(たとえば、型が異なり、そのためにの値がor に_a
割り当てられ_b
ない_c
場合など)に削除できます。
__key
関数の因数分解によるわずかなオーバーヘッドを除いて、これはハッシュと同じくらい高速です。確かに、属性が整数であることがわかっていて、その数が多すぎない場合は、いくつかのホームロールハッシュを使用すると、わずかに速く実行できる可能性がありますが、十分に分散されない可能性があります。hash((self.attr_a, self.attr_b, self.attr_c))
は、小さなsの作成が特別に最適化されているため、驚くほど高速(かつ正しい)でtuple
あり、ハッシュを取得および結合する作業をCビルトインにプッシュします。これは通常、Pythonレベルのコードより高速です。