53

文档说,只要定义了__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不可散列?

请注意,我必须有相同的列表(就常规相等而言)才能散列到相同的值;否则,我将违反哈希函数的这一要求:

唯一需要的属性是比较相等的对象具有相同的哈希值

文档确实警告说,在其生命周期内不应修改可散列对象,当然我不会修改X创建后的实例。当然,解释器无论如何都不会检查。

4

5 回答 5

37

简单地将__hash__方法设置为tuple类的方法是不够的。你实际上并没有告诉它如何以不同的方式散列。元组是可散列的,因为它们是不可变的。如果你真的想让你的具体示例工作,它可能是这样的:

class X2(list):
    def __hash__(self):
        return hash(tuple(self))

在这种情况下,您实际上是在定义如何散列您的自定义列表子类。您只需要准确定义它如何生成哈希即可。您可以随意散列,而不是使用元组的散列方法:

def __hash__(self):
    return hash("foobar"*len(self))
于 2012-04-20T23:04:37.833 回答
21

来自 Python3 文档:

如果一个类没有定义 __eq__() 方法,它也不应该定义 __hash__() 操作;如果它定义了 __eq__() 而不是 __hash__(),它的实例将不能用作可散列集合中的项目。如果一个类定义了可变对象并实现了__eq__()方法,则不应该实现__hash__(),因为实现可散列集合要求键的散列值是不可变的(如果对象的散列值发生变化,那就错了哈希桶)。

参考:object.__hash__(self)

示例代码:

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()))
于 2015-04-03T14:29:58.483 回答
6

根据您的另一个问题,您可以而且应该做的是:不要子类化任何东西,只需封装一个元组。在 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
()
于 2012-04-20T23:33:33.890 回答
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))
于 2012-04-20T23:12:33.150 回答
3

上述答案的补充 - 对于 python3.7+ 中数据类的特定情况 - 要使数据类可散列,您可以使用

@dataclass(frozen=True)
class YourClass:
    pass

作为装饰而不是

@dataclass
class YourClass:
    pass
于 2021-03-04T16:26:17.880 回答