下面当我尝试哈希一个列表时,它给了我一个错误但是使用了一个元组。猜猜它与不变性有关。有人可以详细解释这个吗?
列表
x = [1,2,3]
y = {x: 9}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
元组
z = (5,6)
y = {z: 89}
print(y)
{(5, 6): 89}
答案 0 :(得分:16)
Dicts和其他对象使用hashes来快速存储和检索项目。这一切的机制都发生在#34;封面下#34; - 你作为程序员不需要做任何事情,Python在内部处理它。基本思想是,当您使用{key: value}
创建字典时,Python需要能够散列您用于key
的任何内容,以便它可以快速存储和查找值。
不可变对象或无法更改的对象是可清除的。它们有一个永不改变的唯一值,因此python可以&#34; hash&#34;该值并使用它来有效地查找字典值。属于此类别的对象包括字符串,元组,整数等。你可能会想,&#34;但我可以改变一个字符串!我只是去了mystr = mystr + 'foo'
,但实际上它的作用是创建一个 new 字符串实例并将其分配给mystr
,它不会修改现有的实例。不可变对象永远不会改变,因此您始终可以确保在为不可变对象生成哈希时,通过哈希查找对象将始终返回您开始使用的同一对象,而不是修改后的对象。
您可以自行尝试:hash("mystring")
,hash(('foo', 'bar'))
,hash(1)
可变对象或可以修改的对象,不能可以使用。可以就地修改列表:mylist.append('bar')
或mylist.pop(0)
。您无法安全地对可变对象进行哈希处理,因为您无法保证自上次查看对象后该对象未发生更改。您会发现list
,set
和其他可变类型没有__hash__()
方法。因此,您不能将可变对象用作字典键。
编辑:Eric Duminil的回答提供了一个很好的例子,说明了将可变对象用作字典键所带来的意外行为
答案 1 :(得分:10)
以下是为什么允许将可变类型作为键可能不是一个好主意的示例。在某些情况下,此行为可能很有用,但也可能导致令人惊讶的结果或错误。
通过在__hash__
的子类上定义list
,可以将数字列表用作关键字:
class MyList(list):
def __hash__(self):
return sum(self)
my_list = MyList([1, 2, 3])
my_dict = {my_list: 'a'}
print my_dict.get(my_list)
# 'a'
my_list[2] = 4 # __hash__() becomes 7
print my_dict.keys()[0]
# [1, 2, 4]
print my_dict.get(my_list)
# None
print my_dict.get(MyList([1,2,3]))
# None
my_list[0] = 0 # __hash_() is 6 again, but for different elements
print my_dict.keys()[0]
# [0, 2, 4]
print my_dict.get(my_list)
# 'a'
在Ruby中,它允许使用列表作为键。 Ruby列表称为Array
,dict是Hash
,但语法与Python非常相似:
my_list = [1]
my_hash = { my_list => 'a'}
puts my_hash[my_list]
#=> 'a'
但是如果修改了这个列表,那么即使密钥仍在dict中,dict也不会再找到相应的值:
my_list << 2
puts my_list
#=> [1,2]
puts my_hash.keys.first
#=> [1,2]
puts my_hash[my_list]
#=> nil
可以强制dict再次计算键哈希值:
my_hash.rehash
puts my_hash[my_list]
#=> 'a'
答案 2 :(得分:4)
哈希集计算对象的哈希,并根据该哈希将对象存储在结构中以便快速查找。因此,通过合同,一旦将对象添加到字典中,就不允许更改哈希。大多数好的哈希函数将取决于元素的数量和元素本身。
元组是不可变的,所以在构造之后,值不能改变,因此哈希也不能改变(或者至少一个好的实现不应该让哈希改变)。
另一方面,列表是 mutable :可以在以后添加/删除/更改元素。因此,哈希可以改变违反合同。
因此,所有不能保证在添加对象后保持稳定的哈希函数的对象违反了契约,因此不是好的候选者。因为对于查找,字典将首先计算密钥的哈希值,并确定正确的桶。如果同时更改了密钥,则可能导致漏报:对象在字典中,但由于散列不同而无法再检索,因此将搜索与最初添加对象的桶不同的桶。
答案 3 :(得分:0)
因为列表是可变的,而元组则不是。当您将值的哈希值存储在例如dict中时,如果对象发生更改,则存储的哈希值不会找到,因此它将保持不变。下次查找对象时,字典将尝试通过旧的哈希值查找它,这不再相关。
为了防止这种情况,python不允许你拥有可变项。
答案 4 :(得分:0)
我想补充以下方面,因为它已经没有涵盖其他答案。
将可变对象设为可以删除并没有什么不对,它只是不明确,这就是为什么它需要由程序员自己(而不是编程语言)一致地定义和实现。
请注意,您可以为任何自定义类实现__hash__
方法,该类允许将其实例存储在需要可哈希类型的上下文中(例如dict键或集合)。
哈希值通常用于确定两个对象是否代表相同的东西。所以请考虑以下示例。您有一个包含两个项目的列表:l = [1, 2]
。现在,您将一个项目添加到列表中:l.append(3)
。现在你必须回答以下问题:这仍然是一回事吗?两者 - 是和否 - 都是有效的答案。 &#34;是&#34;,它仍然是相同的列表和&#34; no&#34;,它已不再具有相同的内容。
所以这个问题的答案取决于你作为程序员,因此你可以手动为你的可变类型实现哈希方法。
答案 5 :(得分:0)
如果对象具有在其生命周期内永远不会更改的哈希值(它需要__hash __()方法),并且可以与其他对象进行比较(它需要__eq __()方法),则该对象是可清除的。比较相等的Hashable对象必须具有相同的哈希值。
所有Python的不可变内置对象都是可清除的;可变容器(例如列表或字典)不是。