是什么让一个用户定义的类unhashable?(What makes a user-defined

2019-07-29 02:36发布

的文档说,一类是可哈希只要它定义__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 unhashable?

请注意,我必须有相同的列表(在常规平等的条件下)被散列为相同的值; 否则,我会违反有关规定的哈希函数:

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

该文档也警告说,一个哈希的对象不应该在其一生中进行修改,当然我不修改的情况下, X创建后。 当然,解释器将不检查反正。

Answer 1:

只需将设置__hash__方法来使的tuple类是不够的。 你还没有真正告诉它如何以不同的哈希任何。 因为它们是不可变的元组哈希的。 如果你真的想让你的具体例子的工作,它可能是这样的:

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

在这种情况下,你实际上是如何定义散列您的自定义列表的子类。 你一定要确定它究竟是如何产生的哈希值。 您可以在哈希任何你想要的,而不是使用元组的哈希方法:

def __hash__(self):
    return hash("foobar"*len(self))


Answer 2:

从Python3文档:

如果一个类没有定义__eq __()方法不应该限定无论是__hash __()操作; 如果它定义__eq __(),但不__hash __(),它的实例将不能用作在可哈希集合的项目。 如果一个类定义了可变对象,并实现了__eq __()方法,它不应该执行__hash __(),因为哈希的集合的实现需要一个关键的哈希值是不可变的(如果对象的散列值发生变化,这将是错误的哈希桶)。

参考文献: 对象.__哈希__(个体经营)

示例代码:

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()))


Answer 3:

你可以和应该做的,根据你的另一个问题,就是:不要继承什么,只是封装的元组。 这是完全正常的在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
()


Answer 4:

如果不修改的情况下, 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

据我了解,蟒蛇内部,由于某种原因,被检测X具有__hash____eq__方法,但不要求他们。

所有这一切的寓意是:只写一个真正的散列函数。 由于这是一个序列对象,将其转换为一个数组和散列,这是最明显的方法。

def __hash__(self):
    return hash(tuple(self))


文章来源: What makes a user-defined class unhashable?