【问题标题】:Make a list mutable and hashable使列表可变且可散列
【发布时间】:2015-05-29 19:32:46
【问题描述】:

一个可散列的对象需要一个__hash__ 方法,并且它有一个在其生命周期内永远不会改变的散列值。

由于我完全忽略的原因,Python 列表不可散列,我想知道以下实现是否正常,或者它是否有一些我不知道的故障。

class L(list):
    def __hash__(self):
        return id(self)

 a = range(10)
 l = L(range(10))
 print a
 >> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 print l
 >> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 hash(l)
 >> 41889288
 hash(a) # unsurprisingly it returns an error because a list is not hashable
 >> Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    TypeError: unhashable type: 'list'

 # lets append a value to l
 l.append(1000)
 print l
 >> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1000]
 isinstance(l, list) # to double check whether l is a List instance
 >> True
 D = {}
 D[l] = "this is a dict with a list as key"
 print D
 {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1000]: 'this is a dict with a list as key'}
 # lets append another value to l
 l.append(-10)
 print D
 >> {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1000, -10]: 'this is a dict with a list as key'}

 # now lets add a normal list to the dict to the dict
 D[a] = "Add a list as key"
 >>Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
   TypeError: unhashable type: 'list'

所以这是一个可散列的列表实现,几乎没有问题,我不明白为什么一个列表在正常的 Python 发行版中不能是可散列的,即使它仍然是可变的。

注意:这个问题不是关于为什么列表不能用作字典键 有什么解释吗?

【问题讨论】:

  • 什么是可散列和可变的?如果列表改变而哈希没有改变,你怎么能有意义地将它用作字典键?字典和哈希表的整个概念在这里分解。哈希是用来区分一个键和另一个键的。如果 key 改变了而 hash 没有改变,那会发生什么?
  • 换句话说,根据你的想法,假设我创建了一个字典d,并分配d[[1,2,3]] = 'alpha'。 (也就是说,键[1, 2, 3] 指的是'alpha'。)然后我追加到列表中。 d[[1, 2, 3, 4]] 仍然是 alpha 吗?如果我创建一个新列表[1, 2, 3, 4],它可能具有不同的哈希值,然后将其添加到字典中怎么办?然后呢?
  • 从技术上讲,您是对的,但如果您将其视为实例,则密钥不会改变。它的值在变化,但实例本身没有变化
  • 谷歌搜索“python 列表作为字典键”让我找到了this SO question,这让我找到了this Python Wiki entry,很好地解释了为什么像list 这样的可变数据类型不是一个好的选择用于字典键。
  • 它是可散列的point是什么?列表不是不可散列的,因为它很难,而是因为它没有意义。

标签: python python-2.7 mutable hashable


【解决方案1】:

你有一个__hash__ 方法。它返回一些东西。它会返回有用的东西吗?让我们看看。

>>> a = L([1, 2])
>>> b = L([1, 2])
>>> a == b
True
>>> s = set([a, b])
>>> s
set([[1, 2], [1, 2]])

不!它打破了使用hash 的任何代码所做的假设,特别是相等的对象具有相等的哈希值。现在我们有一个包含两个相等对象的集合。您可以通过使==!= 按身份进行比较来解决此问题,但是您也会丢失总顺序,并且您必须删除&lt;&gt;&lt;=&gt;=。这是很多有用的东西,你必须拿出来使列表有意义地可散列。

【讨论】:

  • range(10) == range(10) 也返回 True 但 range(10) 是 range(10) 以同样的方式返回 False L(range(10)) 是 L(range(10) ) 返回 False 我看不出这里有什么问题...
  • @Cobry:是的,但你不能把它们放在一个集合中,所以你不能把它们放在同一个集合中并破坏集合 API。
  • @user2357112:嗯,在 Python 3 中你可以。 ;-)
  • @Cobry:此外,不假定相等的对象具有相等的 ID。假设对于任何对象aba == b 意味着hash(a) == hash(b)。这个属性是哈希的全部意义和它们有用的全部原因;你的__hash__ 没有这个属性。
【解决方案2】:

您可以(但有限制)使用 JSON 转储来比较内容质量。一个限制是这仅适用于任何 JSON 可序列化数据。

import json

class HashableList(list):
    def __hash__(self):
        return hash(json.dumps(self, sort_keys=True))

这在大多数情况下都很有效。在任何情况下,您都需要确保这适用于您正在使用的数据。

【讨论】:

    猜你喜欢
    • 2016-09-05
    • 2017-01-24
    • 1970-01-01
    • 2012-05-02
    • 2012-09-25
    • 2015-07-05
    • 2017-07-11
    • 2015-11-22
    相关资源
    最近更新 更多