次のようなnamedtuple
があるとします。
EdgeBase = namedtuple("EdgeBase", "left, right")
これにカスタムハッシュ関数を実装したいので、次のサブクラスを作成します。
class Edge(EdgeBase):
def __hash__(self):
return hash(self.left) * hash(self.right)
オブジェクトは不変なので、ハッシュ値を1回だけ計算したいので、次のようにします。
class Edge(EdgeBase):
def __init__(self, left, right):
self._hash = hash(self.left) * hash(self.right)
def __hash__(self):
return self._hash
これは機能しているようですが、Pythonでのサブクラス化と初期化について、特にタプルについては、私には本当にわかりません。このソリューションに落とし穴はありますか?これを行うための推奨される方法はありますか?元気?前もって感謝します。
2017年の編集:namedtuple
は素晴らしいアイデアではないことが判明 。 attrs は、現代的な代替手段です。
class Edge(EdgeBase):
def __new__(cls, left, right):
self = super(Edge, cls).__new__(cls, left, right)
self._hash = hash(self.left) * hash(self.right)
return self
def __hash__(self):
return self._hash
__new__
は、タプルは不変であるため、ここで呼び出すものです。不変オブジェクトは、__new__
にデータが入力される代わりに、__init__
で作成され、ユーザーに返されます。
__new__
は、歴史的/奇妙な理由から暗黙的にcls
であるため、super
は__new__
のstaticmethod
呼び出しに2回渡す必要があります。
問題のコードは、多重継承の状況でサブクラス化される場合に備えて、__init__
のスーパーコールから恩恵を受ける可能性がありますが、それ以外の場合は正しいです。
class Edge(EdgeBase):
def __init__(self, left, right):
super(Edge, self).__init__(left, right)
self._hash = hash(self.left) * hash(self.right)
def __hash__(self):
return self._hash
タプルは読み取り専用ですが、サブクラスのタプル部分のみが読み取り専用ですが、他のプロパティは通常どおりに書き込むことができます。これにより、__init__
または__new__
で行われるかどうかに関係なく、_hashへの割り当てが可能になります。 __slots__
を()に設定することにより、サブクラスを完全に読み取り専用にすることができます。これにより、メモリを節約できるという利点がありますが、_hashに割り当てることはできません。
Python 3.7+)では、 dataclasses を使用してハッシュ可能なクラスを簡単に構築できるようになりました。
コード
int
タイプのleft
およびright
を想定して、_unsafe_hash
_を介したデフォルトのハッシュを使用します+ キーワード:
_import dataclasses as dc
@dc.dataclass(unsafe_hash=True)
class Edge:
left: int
right: int
hash(Edge(1, 2))
# 3713081631934410656
_
これで、これらの(可変)ハッシュ可能なオブジェクトをセットの要素または(dictのキー)として使用できます。
_{Edge(1, 2), Edge(1, 2), Edge(2, 1), Edge(2, 3)}
# {Edge(left=1, right=2), Edge(left=2, right=1), Edge(left=2, right=3)}
_
詳細
あるいは、___hash__
_関数をオーバーライドできます。
_@dc.dataclass
class Edge:
left: int
right: int
def __post_init__(self):
# Add custom hashing function here
self._hash = hash((self.left, self.right)) # emulates default
def __hash__(self):
return self._hash
hash(Edge(1, 2))
# 3713081631934410656
_
@ShadowRangerのコメントを拡張すると、OPのカスタムハッシュ関数は信頼できません。特に、属性値は交換できます。 hash(Edge(1, 2)) == hash(Edge(2, 1))
、これは意図したものではない可能性があります。
+「unsafe」という名前は、オブジェクトが変更可能であっても、デフォルトのハッシュが使用されることを示しています。これは、特に不変のキーを期待するdictでは望ましくない場合があります。不変ハッシュは、適切なキーワードでオンにすることができます。データクラスの ハッシュロジック および 関連する問題 の詳細も参照してください。