为什么列表不可用?

时间:2016-08-23 15:09:57

标签: python list hash

关于SO的一个常见问题是removing duplicates from a list of lists。由于列表不可删除,set([[1, 2], [3, 4], [1, 2]])会抛出TypeError: unhashable type: 'list'。这类问题的答案通常涉及使用元组,这些元组是不可变的,因此可以使用。

What makes lists unhashable?的答案包括以下内容:

  

如果哈希值在字典中的特定插槽中存储后发生更改,则会导致字典不一致。例如,最初列表将存储在位置A,该位置是基于散列值确定的。如果哈希值发生变化,如果我们查找列表,我们可能无法在位置A找到它,或者根据新的哈希值,我们可能会找到其他对象。

但我不太明白,因为可以使用其他可用于字典键的类型而不会出现问题:

>>> d = {}
>>> a = 1234
>>> d[a] = 'foo'
>>> a += 1
>>> d[a] = 'bar'
>>> d
{1234: 'foo', 1235: 'bar'}

很明显,如果a的值发生变化,它将散列到字典中的其他位置。 为什么同样的假设会对列表造成危险?为什么以下是散列列表的不安全方法,因为无论如何我们都会使用它?

>>> class my_list(list):
...   def __hash__(self):
...     return tuple(self).__hash__()
...
>>> a = my_list([1, 2])
>>> b = my_list([3, 4])
>>> c = my_list([1, 2])
>>> foo = [a, b, c]
>>> foo
[[1, 2], [3, 4], [1, 2]]
>>> set(foo)
set([[1, 2], [3, 4]])

这似乎解决了set()问题,为什么这是一个问题?列表可能是可变的,但它们是有序的,似乎它们都需要进行散列。

1 个答案:

答案 0 :(得分:11)

您似乎将可变性与重新绑定相混淆。 a += 1将{strong>新对象,int对象的数值为1235,分配给a。在幕后,对于int等不可变对象,a += 1a = a + 1相同。

原始1234对象未发生变异。字典仍然使用数值为1234的int对象作为键。尽管a现在引用了另一个对象,但该字典仍然包含该对象的引用。这两个参考文献是独立的。

请改为尝试:

>>> class BadKey:
...     def __init__(self, value):
...         self.value = value
...     def __eq__(self, other):
...         return other == self.value
...     def __hash__(self):
...         return hash(self.value)
...     def __repr__(self):
...         return 'BadKey({!r})'.format(self.value)
...
>>> badkey = BadKey('foo')
>>> d = {badkey: 42}
>>> badkey.value = 'bar'
>>> print(d)
{BadKey('bar'): 42}

请注意,我更改了value实例上的属性badkey。我甚至都没碰过字典。字典反映了这种变化; 实际键值本身发生了变异,名称为badkey和字典引用的对象。

但是,您现在无法再访问该密钥

>>> badkey in d
False
>>> BadKey('bar') in d
False
>>> for key in d:
...     print(key, key in d)
...
BadKey('bar') False

我彻底破坏了我的字典,因为我无法再可靠地找到密钥。

这是因为BadKey违反了 hashability 的原则;哈希值必须保持稳定。如果您不更改有关散列所基于的对象的任何内容,则只能执行此操作。并且散列必须基于使两个实例相等的任何内容。

对于列表,内容使两个列表对象相等。你可以改变它们,所以你也不能产生稳定的哈希值。