为什么Python的集合不可哈希?

96

我偶然看到一个博客文章详细介绍了如何在Python中实现幂集函数。于是我试图用自己的方法去尝试,但发现Python显然不能有一组集合,因为集合不可哈希。这很烦人,因为幂集的定义是它是一组集合,并且我想使用实际的集合操作来实现它。

>>> set([ set() ])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'set'

为什么Python的集合不能作为哈希表的键?


4
如果一个键值不是不可变的,通常会表现得很糟糕。如果必须使用,则可以使用元组。 - Chris Eberle
4个回答

167

通常在Python中,只有不可变对象才能被哈希。 set() 的不可变版本 -- frozenset() -- 是可以被哈希的。


8
请参阅Python FAQ条目为什么字典键必须是不可变的? - abarnert

36
因为它们是可变的。如果它们是可哈希的,哈希可能会悄悄地变得“无效”,这基本上会使哈希变得毫无意义。

21

来自Python文档:

可哈希
如果一个对象在其生命周期内具有永不更改的哈希值(需要哈希()方法),且可以与其他对象进行比较(需要eq()或cmp()方法),则该对象是可哈希的。相等的可哈希对象必须具有相同的哈希值。

可哈希性使得对象可用作字典键和集合成员,因为这些数据结构在内部使用哈希值。

所有Python的不可变内置对象都是可哈希的,而没有可变容器(如列表或字典)可哈希。默认情况下,用户定义类的实例都是可哈希的;它们都是不相等的,并且其哈希值是它们的id()。


7

如果有帮助的话... 如果您确实需要将不可哈希的东西转换为可哈希的等价物,那么您可能会做出以下操作:

from collections import Hashable, MutableSet, MutableSequence, MutableMapping

def make_hashdict(value):
    """
    Inspired by https://dev59.com/nnM_5IYBdhLWcg3w9oQA
     - with the added bonus that it inherits from the dict type of value
       so OrderedDict's maintain their order and other subclasses of dict() maintain their attributes
    """
    map_type = type(value)

    class HashableDict(map_type):
        def __init__(self, *args, **kwargs):
            super(HashableDict, self).__init__(*args, **kwargs)
        def __hash__(self):
            return hash(tuple(sorted(self.items())))

    hashDict = HashableDict(value)

    return hashDict


def make_hashable(value):
    if not isinstance(value, Hashable):
        if isinstance(value, MutableSet):
            value = frozenset(value)
        elif isinstance(value, MutableSequence):
            value = tuple(value)
        elif isinstance(value, MutableMapping):
            value = make_hashdict(value)

        return value

my_set = set()
my_set.add(make_hashable(['a', 'list']))
my_set.add(make_hashable({'a': 1, 'dict': 2}))
my_set.add(make_hashable({'a', 'new', 'set'}))

print my_set

我的HashableDict实现是来自于这里最简单、最不严谨的例子。如果您需要一个更高级的HashableDict,支持pickling和其他功能,请查看许多其他的实现。在我上面的版本中,我想保留原始的dict类,从而保留OrderedDicts的顺序。我还使用了来自这里的AttrDict进行类似属性的访问。

上述示例不具有任何权威性,只是我解决类似问题时的解决方案,其中我需要在集合中存储一些东西,并需要先将它们“哈希化”。


由于使用了sorted,这需要对值进行排序。有些对象定义了__eq____hash__但没有定义顺序(例如__le__等)。你可以使用hash(frozenzet(self.items()))代替。 - Albert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接