docs表示只要定义__hash__
方法和__eq__
方法,类就可以清除。但是:
class X(list):
# read-only interface of `tuple` and `list` should be the same, so reuse tuple.__hash__
__hash__ = tuple.__hash__
x1 = X()
s = {x1} # TypeError: unhashable type: 'X'
是什么让X
不可用?
请注意,我必须将相同的列表(在正则相等方面)散列为相同的值;否则,我将violate this requirement关于哈希函数:
唯一需要的属性是比较相等的对象 相同的哈希值
文档警告说,可以在其生命周期内修改可清除对象,当然,我不会在创建后修改X
的实例。当然,翻译无论如何都不会检查。
答案 0 :(得分:19)
仅仅将__hash__
方法设置为tuple
类的方法是不够的。你还没有真正告诉它如何以不同方式进行哈希。元组是可以清除的,因为它们是不可变的。如果你真的想让你的具体例子工作,可能是这样的:
class X2(list):
def __hash__(self):
return hash(tuple(self))
在这种情况下,您实际上是在定义如何散列自定义列表子类。您只需要准确定义它如何生成哈希。你可以使用任何你想要的哈希,而不是使用元组的哈希方法:
def __hash__(self):
return hash("foobar"*len(self))
答案 1 :(得分:9)
来自Python3文档:
如果一个类没有定义__eq __()方法,则不应该定义一个 __hash __()操作;如果它定义__eq __()但不定义__hash __(),则其实例将不能用作hashable集合中的项目。如果一个类定义了可变对象并实现了一个 __eq __()方法,它不应该实现__hash __(),因为hashable集合的实现需要一个键的哈希 value是不可变的(如果对象的哈希值改变,它将在 错误的哈希桶。)
示例代码:
class Hashable:
pass
class Unhashable:
def __eq__(self, other):
return (self == other)
class HashableAgain:
def __eq__(self, other):
return (self == other)
def __hash__(self):
return id(self)
def main():
# OK
print(hash(Hashable()))
# Throws: TypeError("unhashable type: 'X'",)
print(hash(Unhashable()))
# OK
print(hash(HashableAgain()))
答案 2 :(得分:5)
根据您的其他问题,您可以做和应该做的是: 不要子类化任何东西,只需封装一个元组。在init中完成这项工作非常好。
class X(object):
def __init__(self, *args):
self.tpl = args
def __hash__(self):
return hash(self.tpl)
def __eq__(self, other):
return self.tpl == other
def __repr__(self):
return repr(self.tpl)
x1 = X()
s = {x1}
产生:
>>> s
set([()])
>>> x1
()
答案 3 :(得分:3)
如果在创建后没有修改X
的实例,为什么不对子元进行子类化?
但是我会指出这实际上并没有引发错误,至少在Python 2.6中是这样。
>>> class X(list):
... __hash__ = tuple.__hash__
... __eq__ = tuple.__eq__
...
>>> x = X()
>>> s = set((x,))
>>> s
set([[]])
我毫不犹豫地说“有效”,因为这不符合你的想法。
>>> a = X()
>>> b = X((5,))
>>> hash(a)
4299954584
>>> hash(b)
4299954672
>>> id(a)
4299954584
>>> id(b)
4299954672
它只是将对象id用作哈希。当你实际打电话给__hash__
时,你仍会收到错误;同样适用于__eq__
。
>>> a.__hash__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: descriptor '__hash__' for 'tuple' objects doesn't apply to 'X' object
>>> X().__eq__(X())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: descriptor '__eq__' for 'tuple' objects doesn't apply to 'X' object
我认为python内部由于某种原因检测到X
有__hash__
和__eq__
方法,但没有调用它们。
所有这一切的道德是:只写一个真正的哈希函数。由于这是一个序列对象,因此将其转换为元组和散列,这是最明显的方法。
def __hash__(self):
return hash(tuple(self))
答案 4 :(得分:1)
上述答案的补充-对于python3.7+中数据类的特定情况-使数据类可散列,您可以使用
@dataclass(frozen=True)
class YourClass:
pass
作为装饰代替
@dataclass
class YourClass:
pass