如何使 python 数据类可哈希?

新手上路,请多包涵

假设我在 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'

原文由 Brian C. 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 897
2 个回答

文档

以下是管理隐式创建 __hash__() 方法的规则:

[…]

If eq and frozen are both true, by default dataclass() will generate a __hash__() method for you. If eq is true and frozen is false, __hash__() will be set to None , marking it unhashable (which it is, since it是可变的)。如果 eq 为假, __hash__() 将保持不变意味着 __hash__() 超类的方法将被使用(如果超类是对象)回到基于 id 的散列)。

由于您设置了 eq=True 并在默认情况下保留了 frozen ( False ),因此您的数据类是不可散列的。

您有 3 个选择:

  • 设置 frozen=True (除了 eq=True 之外),这将使您的类不可变且可散列。
  • 设置 unsafe_hash=True ,这将创建一个 __hash__ 方法,但让你的类可变,因此如果你的类的实例在存储在字典或集合中时被修改,就会有问题:
   cat = Category('foo', 'bar')
  categories = {cat}
  cat.id = 'baz'

  print(cat in categories)  # False

  • 手动实现一个 __hash__ 方法。

原文由 Aran-Fey 发布,翻译遵循 CC BY-SA 4.0 许可协议

TL;博士

frozen=Trueeq=True 结合使用(这将使实例不可变)。

长答案

文档

__hash__() 由内置 hash() 使用,当对象被添加到散列集合(如字典和集合)时。拥有 __hash__() 意味着该类的实例是不可变的。可变性是一个复杂的属性,它取决于程序员的意图、 __eq__() 的存在和行为,以及 dataclass() 装饰器中的 eq 和 frozen 标志的值。

默认情况下, dataclass() 不会隐式添加 __hash__() 方法,除非这样做是安全的。它也不会添加或更改现有的明确定义的 __hash__() 方法。设置类属性 __hash__ = None 对 Python 具有特定含义,如 __hash__() 文档中所述。

如果 __hash__() 没有显式定义,或者如果它设置为 None,那么 dataclass() 可能会添加一个隐式的 __hash__() 方法。虽然不推荐,但您可以强制 dataclass() 创建一个 __hash__() 方法 unsafe_hash=True 。如果您的类在逻辑上是不可变的但仍然可以改变,则可能是这种情况。这是一个特殊的用例,应该仔细考虑。

以下是管理隐式创建 __hash__() 方法的规则。请注意,您不能在数据类中同时具有显式 __hash__() 方法并设置 unsafe_hash=True ;这将导致 TypeError

如果 eq 和 frozen 都为真,默认情况下 dataclass() 将为您生成一个 __hash__() 方法。如果 eq 为真而 frozen 为假, __hash__() 将被设置为 None,将其标记为不可散列(它是不可散列的,因为它是可变的)。如果 eq 为 false, __hash__() 将保持不变,这意味着将使用超类的 __hash__() 方法(如果超类是对象,这意味着它将回退到基于 id 的哈希).

原文由 DeepSpace 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题