关于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()
问题,为什么这是一个问题?列表可能是可变的,但它们是有序的,似乎它们都需要进行散列。
答案 0 :(得分:11)
您似乎将可变性与重新绑定相混淆。 a += 1
将{strong>新对象,int
对象的数值为1235,分配给a
。在幕后,对于int
等不可变对象,a += 1
与a = 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 的原则;哈希值必须保持稳定。如果您不更改有关散列所基于的对象的任何内容,则只能执行此操作。并且散列必须基于使两个实例相等的任何内容。
对于列表,内容使两个列表对象相等。你可以改变它们,所以你也不能产生稳定的哈希值。