如何使Python数据类可哈希?

问题描述 投票:0回答:3

假设我在 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'
python python-3.x hash python-dataclasses
3个回答
139
投票

来自文档

以下是管理

__hash__()
方法隐式创建的规则:

[...]

如果

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

由于您设置了

eq=True
并将
frozen
保留为默认值 (
False
),因此您的数据类是不可散列的。

您有 3 个选择:

  • 设置
    frozen=True
    (除了
    eq=True
    之外),这将使您的类不可变且可散列。
  • Set

    unsafe_hash=True
    ,这将创建一个
    __hash__
    方法,但使您的类可变,因此,如果您的类的实例在存储在字典或集合中时被修改,则可能会出现问题:

    cat = Category('foo', 'bar')
    categories = {cat}
    cat.id = 'baz'
    
    print(cat in categories)  # False
    
  • 手动实现
    __hash__
    方法。

39
投票

TL;博士

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

长答案

来自文档

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

默认情况下,

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

如果

__hash__()
没有显式定义,或者设置为 None,则
dataclass()
可以添加隐式
__hash__()
方法。虽然不是 推荐,您可以强制
dataclass()
创建
__hash__()
方法 与
unsafe_hash=True
。如果您的班级是 逻辑上是不可变的,但仍然可以改变。这是一个 专门的用例,应仔细考虑。

以下是管理

__hash__()
方法隐式创建的规则。 请注意,您不能在您的 数据类并设置
__hash__()
;这将导致
unsafe_hash=True
如果 eq 和 freeze 都为 true,默认情况下 

TypeError

将生成一个

dataclass()
方法给你。如果 eq 为 true 并且 freeze 为 false,则
__hash__()
将被设置为 None,将其标记为不可散列(确实如此,因为它是可变的)。如果 eq 为 false,则
__hash__()
将被留下 不变意味着将使用超类的
__hash__()
方法 (如果超类是 object,这意味着它将回退到基于 id 的 散列)。

    


37
投票

您可以通过设置compare=False或hash=False来排除字段进行哈希比较。 (hash默认继承自compare)。

如果您将节点存储在图中,但希望在不破坏其散列的情况下将它们标记为已访问(例如,如果它们位于一组未访问的节点中......),这可能很有用。

__hash__()

这花了我
几个小时

来弄清楚......我发现有用的进一步阅读是关于数据类的Python文档。具体请参阅字段文档和 dataclass arg 文档。 https://docs.python.org/3/library/dataclasses.html

© www.soinside.com 2019 - 2024. All rights reserved.