如何使python数据类可哈希化?

时间:2018-09-18 16:03:39

标签: python python-3.x python-dataclasses

说一个我在python3中有一个数据类。我希望能够哈希和排序这些对象。

我只希望它们按ID排序/散列。

我在文档中看到,我可以只实现__hash__以及所有其他功能,但是我想让datacalses为我完成工作,因为它们旨在解决这个问题。

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'

3 个答案:

答案 0 :(得分:6)

来自the docs

  

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

     

[...]

     

如果eqfrozen均为真,则默认情况下dataclass()将   为您生成一个__hash__()方法。如果eq为true并且frozen   为假,__hash__()将设置为None,将其标记为不可散列   (它是可变的,因为它是可变的)。如果eq为假,则__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__方法。

答案 1 :(得分:2)

TL; DR

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

长期回答

来自docs

  

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

     

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

     

如果未明确定义__hash__(),或者将其设置为“无”,则   __hash__()可以添加一个隐式dataclass()方法。虽然没有   建议,您可以强制__hash__()创建一个dataclass()方法   与__hash__()。如果您的班级是   从逻辑上讲是不可变的,但仍然可以更改。这是一个   专业用例,应仔细考虑。

     

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

     

如果eq和Frozen均为true,则默认情况下TypeError将生成一个   dataclass()方法适合您。如果eq为true,且Frozen为false,则__hash__()将设置为None,将其标记为不可散列(因为它是可变的,因此是不可散列的)。如果eq为false,则__hash__()将保留   保持不变,表示将使用超类的__hash__()方法   (如果超类是对象,这意味着它将退回到基于id   哈希)。

答案 2 :(得分:0)

我想添加一个有关使用unsafe_hash的特殊说明。

您可以通过设置compare = False或hash = False来排除字段以进行哈希比较。 (默认情况下,哈希是从compare继承的。)

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

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文档。具体请参见现场文档和数据类arg文档。 https://docs.python.org/3/library/dataclasses.html