Bri*_* C. 13 python hash 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'
Run Code Online (Sandbox Code Playgroud)
Dee*_*ace 20
TL; 博士
frozen=True
与 to 结合使用eq=True
(这将使实例不可变)。
长答案
从文档:
__hash__()
由 built-in 使用hash()
,当对象被添加到散列集合(如字典和集合)时。拥有 a__hash__()
意味着该类的实例是不可变的。可变性是一个复杂的属性,它取决于程序员的意图、 的存在和行为__eq__()
,以及dataclass()
装饰器中eq 和frozen 标志的值。默认情况下,
dataclass()
不会隐式添加__hash__()
方法,除非这样做是安全的。它也不会添加或更改现有的明确定义的__hash__()
方法。设置 class 属性__hash__ = None
对 Python 有特定的意义,如__hash__()
文档中所述。如果
__hash__()
没有显式定义,或者设置为None,则dataclass()
可以添加隐式__hash__()
方法。虽然不建议,您可以强制dataclass()
创建__hash__()
与方法unsafe_hash=True
。如果您的类在逻辑上是不可变的,但仍然可以改变,则可能就是这种情况。这是一个专门的用例,应该仔细考虑。以下是管理
__hash__()
方法隐式创建的规则。请注意,您__hash__()
的数据类和 set 中不能同时具有显式方法unsafe_hash=True
;这将导致一个TypeError
.如果 eq 和frozen 都为真,默认情况下
dataclass()
会__hash__()
为你生成一个 方法。如果 eq 为真,而frozen 为假,__hash__()
则将设置为 None,将其标记为不可散列(确实如此,因为它是可变的)。如果 eq 为假,__hash__()
将保持不变__hash__()
,这意味着将使用超类的方法(如果超类是对象,这意味着它将回退到基于 id 的散列)。
Ara*_*Fey 15
从文档:
以下是管理
__hash__()
方法隐式创建的规则:[...]
如果
eq
和frozen
均为true,则默认情况下dataclass()
会__hash__()
为您生成一个方法。如果eq
为true和frozen
false,__hash__()
则将其设置为None
,将其标记为不可哈希(因为它是可变的,因此它是不可哈希的)。如果eq
为false,__hash__()
将保持不变__hash__()
,这意味着将使用超类的方法(如果超类是object,则意味着它将退回到基于id的哈希)。
由于您设置eq=True
并保留frozen
了默认值(False
),因此您的数据类不可哈希。
您有3种选择:
frozen=True
(除了eq=True
),这将使您的类不可变且可哈希化。Set unsafe_hash=True
,它将创建一个__hash__
方法,但使您的类保持可变,因此,如果在存储在dict或set中的同时修改了您的类的实例,则可能会出现问题:
cat = Category('foo', 'bar')
categories = {cat}
cat.id = 'baz'
print(cat in categories) # False
Run Code Online (Sandbox Code Playgroud)__hash__
方法。Leo*_*sev 13
我想为 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.")
Run Code Online (Sandbox Code Playgroud)
这花了我几个小时才弄清楚......我发现有用的进一步阅读是关于数据类的python doc。具体参见 field 文档和 dataclass arg 文档。 https://docs.python.org/3/library/dataclasses.html