是什么让用户定义的类不可用?

时间:2012-04-20 22:58:40

标签: python class python-3.x user-defined-types hashable

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的实例。当然,翻译无论如何都不会检查。

5 个答案:

答案 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是不可变的(如果对象的哈希值改变,它将在   错误的哈希桶。)

参考: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()))

答案 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