web-dev-qa-db-ja.com

python dataclassをハッシュ可能にするにはどうすればよいですか?

私がpython3にデータクラスを持っているとしましょう。これらのオブジェクトをハッシュして順序付けできるようにしたい。

私はそれらをidで注文/ハッシュ化したいだけです。

私はドキュメントで__hash__とそれ以外のすべてを実装できることを確認しましたが、datacalssesがこれを処理することを目的としているため、私のために作業を実行してもらいたいと思います。

from dataclasses import dataclass, field

@dataclass(eq=True, order=True)
class Category:
    id: str = field(compare=True)
    name: str = field(default="set this in post_init", compare=False)

a = sorted(list(set([ Category(id='x'), Category(id='y')])))

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'Category'
26
Brian C.

the docs から:

__hash__()メソッドの暗黙的な作成を管理するルールは次のとおりです。

[...]

eqfrozenの両方がtrueの場合、デフォルトでdataclass()__hash__()メソッドを生成します。 eqがtrueでfrozenがfalseの場合、__hash__()Noneに設定され、ハッシュ不可としてマークされます(変更可能であるためです)。 。 eqがfalseの場合、__hash__()はそのまま残り、スーパークラスの__hash__()メソッドが使用されます(スーパークラスがオブジェクトの場合、これはフォールバックすることを意味します) idベースのハッシュへの変換)。

eq=Trueを設定し、frozenをデフォルト(False)のままにしたため、データクラスはハッシュ化できません。

次の3つのオプションがあります。

  • frozen=Trueに加えて)eq=Trueを設定すると、クラスが不変でハッシュ可能になります。
  • unsafe_hash=Trueを設定すると、__hash__メソッドが作成されますが、クラスは変更可能です。そのため、dictまたはsetに保存されているときにクラスのインスタンスが変更されると、問題が発生する危険性があります。

    cat = Category('foo', 'bar')
    categories = {cat}
    cat.id = 'baz'
    
    print(cat in categories)  # False
    
  • __hash__メソッドを手動で実装します。
24
Aran-Fey

TL; DR

_frozen=True_を_eq=True_(インスタンスを不変にする)と組み合わせて使用​​します。

長い答え

docs から:

__hash__()は、組み込みのhash()によって使用され、オブジェクトが辞書やセットなどのハッシュ化されたコレクションに追加されるときに使用されます。 __hash__()があることは、クラスのインスタンスが不変であることを意味します。可変性は、プログラマの意図、__eq__()の存在と動作、およびdataclass()デコレータのeqフラグと凍結フラグの値に依存する複雑なプロパティです。

デフォルトでは、安全な場合を除き、dataclass()は暗黙的に__hash__()メソッドを追加しません。また、明示的に定義された既存の__hash__()メソッドを追加または変更することもありません。クラス属性___hash__ = None_の設定は、__hash__() documentationで説明されているように、Pythonに特定の意味があります。

__hash__()が明示的に定義されていない場合、またはNoneに設定されている場合、dataclass()は暗黙の__hash__()メソッドを追加できます。お勧めしませんが、dataclass()に_unsafe_hash=True_を使用して__hash__()メソッドを作成することを強制できます。これは、クラスが論理的に不変であるにもかかわらず、変更できる場合に当てはまります。これは特殊な使用例であり、慎重に検討する必要があります。

__hash__()メソッドの暗黙的な作成を管理するルールを次に示します。データクラスに明示的な__hash__()メソッドを設定して_unsafe_hash=True_を設定することはできません。これはTypeErrorになります。

Eqとfrozenの両方がtrueの場合、デフォルトでdataclass()__hash__()メソッドを生成します。 eqがtrueでfrozenがfalseの場合、__hash__()はNoneに設定され、ハッシュ不可能としてマークされます(変更可能であるためです)。 eqがfalseの場合、__hash__()はそのまま残り、スーパークラスの__hash__()メソッドが使用されます(スーパークラスがオブジェクトの場合、これはIDベースのハッシュにフォールバックすることを意味します) )。

5
DeepSpace

Unsafe_hashの使用に関する特別な注意事項を追加したいと思います。

Compare = Falseまたはhash = Falseを設定することにより、ハッシュによる比較からフィールドを除外できます。 (デフォルトではハッシュは比較から継承されます)。

これは、ノードをグラフに保存しているが、ハッシュを壊さずに訪問済みとしてマークを付けたい場合に役立ちます(たとえば、未訪問ノードのセットにある場合など)。

from dataclasses import dataclass, field
@dataclass(unsafe_hash=True)
class node:
    x:int
    visit_count: int = field(default=10, compare=False)  # hash inherits compare setting. So valid.
    # visit_count: int = field(default=False, hash=False)   # also valid. Arguably easier to read, but can break some compare code.
    # visit_count: int = False   # if mutated, hashing breaks. (3* printed)

s = set()
n = node(1)
s.add(n)
if n in s: print("1* n in s")
n.visit_count = 11
if n in s:
    print("2* n still in s")
else:
    print("3* n is lost to the void because hashing broke.")

これには時間がかかりました...データクラスに関するpython docです。フィールドのドキュメントとdataclass argのドキュメントを参照してください。 https://docs.python.org/3/library/dataclasses.html

4
Leo Ufimtsev