在Python中,为什么元组可以清除而不是列表?

时间:2017-02-13 12:08:28

标签: python

下面当我尝试哈希一个列表时,它给了我一个错误但是使用了一个元组。猜猜它与不变性有关。有人可以详细解释这个吗?

列表

 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}

6 个答案:

答案 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)。您无法安全地对可变对象进行哈希处理,因为您无法保证自上次查看对象后该对象未发生更改。您会发现listset和其他可变类型没有__hash__()方法。因此,您不能将可变对象用作字典键。

编辑:Eric Duminil的回答提供了一个很好的例子,说明了将可变对象用作字典键所带来的意外行为

答案 1 :(得分:10)

以下是为什么允许将可变类型作为键可能不是一个好主意的示例。在某些情况下,此行为可能很有用,但也可能导致令人惊讶的结果或错误。

的Python

通过在__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)

基于Python Glossary

  

如果对象具有在其生命周期内永远不会更改的哈希值(它需要__hash __()方法),并且可以与其他对象进行比较(它需要__eq __()方法),则该对象是可清除的。比较相等的Hashable对象必须具有相同的哈希值。

     

所有Python的不可变内置对象都是可清除的;可变容器(例如列表或字典)不是。