为什么Python中的字典对象不可哈希?

31

我的意思是为什么我们不能把字典的键设置为另一个字典?

这意味着我们不能有一个将另一个字典作为键的字典...

6个回答

46

简短回答:因为它们是可变容器。

如果字典被散列,当您更改其内容时,其哈希值会发生变化。


2
我甚至不想去想象使用一个对象作为key所需要的恶心逻辑。 - David
8
实际上,Python使得对象哈希变得容易,它为每个对象提供了一个独一无二且恒定的标识符,可用于哈希处理。 - Ben James
6
如果必须要创建字典的可哈希表示,可以使用 frozenset(D.items())(对于字典 D)。frozenset 是一个可哈希且不可变的集合类型 -- 像 dict 一样,由于不可哈希,set 也不能用作字典的键。 - u0b34a0f6ae
一个人可以很容易地为类似于dictobject实现__hash__ - 即使dict项发生更改,也不会改变,因为我们将整个dict作为Key而不是将dict的键作为Key - shahjapan
1
这个答案与不使用frozendict的理由之一相矛盾 - http://legacy.python.org/dev/peps/pep-0416/ - 用户可以“通过约定”不改变共享字典,因此“没有太大的需要”担心这个问题。 - Chris Martin
显示剩余5条评论

20

这很容易处理。在哈希之前,将字典包装在一个frozenset中。然后,当您需要使用它时,将其转换回字典即可。

>>> unhashable = {'b': 'a', 'a': 'b'}
>>> hashable = frozenset(unhashable.items())
>>> unhashable = dict(hashable)
>>> unhashable
{'a': 'b', 'b': 'a'}

请注意,字典键的顺序是未定义的,因此键的变化并不重要。


3
一个被冻结的字典的哈希值不依赖于其值,只依赖于其键。因此,如果你想将一个字典作为另一个字典的键使用,这是行不通的,如果这正是你想要的话。 - JoseOrtiz3
1
如果任何一个项目包含另一个“dict”,它将失败。 - nehem

5

就像其他人所说的那样,字典的哈希值会随着内容的变化而改变。

但是如果你真的需要将字典用作键,你可以子类化字典以创建一个可哈希版本。

>>> class hashabledict(dict):
...    def __hash__(self):
...        return id(self)
... 
>>> hd = hashabledict()
>>> d = dict()
>>> d[hd] = "foo"
>>> d
{{}: 'foo'}

>>> hd["hello"] = "world"
>>> d
{{'hello': 'world'}: 'foo'}

这将用于字典的哈希值替换为对象在内存中的地址。


然后我可以用 hashabledict 替换普通的 dict 吗? - shahjapan
9
但这是无用的:如果我在{}下存储一个值,我不能使用{}查找它,因为两个空的可哈希字典具有不同的ID和不同的哈希值。哈希函数的重要之处在于,对于两个“相等”的值,它必须返回相同的哈希值。 - Ned Batchelder
1
@Ned - 噢!你是对的。真正需要的是一个像frozenset一样运作的frozendict。你可以继承dict来定义一个,就像ASPN上这个配方中所示:http://code.activestate.com/recipes/414283/ - Dave Kirby
2
这可能是有用的,如果你只想区分不同的字典。但要小心; 如果一个字典被垃圾回收了,一个新实例化的字典可能会驻留在内存中相同的位置,从而产生相同的“id()”,引起难以发现的错误。 - Brecht Machiels

1

Python中的可变容器类型都不是可哈希的,因为它们是可变的,因此它们的哈希值可以在其生命周期内发生变化。


4
元组和字符串是容器,但它们是可散列和不可变的。 - Ignacio Vazquez-Abrams
2
@Ignacio Vazquez-Abrams:字符串是序列但不是容器。此外,包含不可哈希项的元组也是不可哈希的;尝试使用([1], {2:3})作为字典键。 - tzot

1
也许出于错误的原因,我遇到了很多次这个问题;我想要引用一个完整的字典作为某个键。我不需要它是可变的,但我希望保留并轻松访问该字典的成员。
我发现将字典转换为JSON(或在您喜欢的替代方式中进行序列化)是使其不可变且快速可用作键值的最简单方法。
例如:
>>> import json
>>> d = {'hey':1, 'there':2}
>>> d_key = json.dumps(d)
>>> d_key
'{"there": 2, "hey": 1}'
>>> d2 = {d_key: 'crazytown'}
>>> d2
{'{"there": 2, "hey": 1}': 'crazytown'}

它很容易被操作,因为它只是一个字符串。如果您想引用其成员,它可以被反序列化为一个对象。

如果字典序列化的顺序不同,这将失败。在字典中,元素的顺序并不重要,但是当您从中创建字符串并将其作为比较对象时,具有相同内容的两个字典可能不匹配。 - Martin Melka
1
@MartinMelka,Python 3.7 中默认使用有序字典,这个说法仍然正确吗? - Cedric H.

0

您可以使用id(inner_dict)作为外部字典的键。

这与Java的行为相同,其中对象的默认哈希码保证对于该对象是唯一的(实际上是内存地址 - 但这是特定于实现的),并用作插入HashMap中的哈希码。

因此,在Java中,您可以有一个HashMap<HashMap<Integer,Integer>,Integer>

在Python中 some_dict = {{1: 1}: 1} 将会给你 TypeError:不可散列类型:'dict'


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