【问题标题】:Why can lists not be used as dictionary keys? [duplicate]为什么列表不能用作字典键? [复制]
【发布时间】:2019-03-30 13:59:11
【问题描述】:

我想要一个作为字典键的列表,定义如下:

data = { 
  [24,48,96]: ["QN.FN.EQ", "OT.AR.LN", "BL.VL.TR"] 
}

这不起作用...错误说是因为“列表类型不可散列...”。

有什么解决方法吗?为了能够像这样从该字典中获取数据:

data[[24,48,96]] # => ["QN.FN.EQ", "OT.AR.LN", "BL.VL.TR"]

我现在唯一的解决方案是 - 将列表转换为字符串并使用字符串作为键。

data = { 
  "24,48,96": ["QN.FN.EQ", "OT.AR.LN", "BL.VL.TR"] 
}
arr = [24,48,96]
print(data[','.join(map(str,arr))])

【问题讨论】:

  • 键应该是不可变的,列表在设计上是可变的。请改用元组。
  • 使用元组 (24, 48, 96) 而不是列表。
  • @Scott 不,我需要tuple
  • 字典通过对键进行散列(计算一个数字)并使用该散列来决定在哪里存储键和值。哈希几乎总是基于对象的内容。因此,如果您将列表作为键,它将根据其内容存储在特定位置。然后,如果列表发生了变异,散列就会改变。如果散列改变了,字典会在错误的地方查找你存储的值。这就是为什么 Python 没有为列表提供 __hash__() 方法的原因。
  • 我稍微编辑了你的问题,[24,48,96] 是一个列表,而不是一个数组。这很重要,因为我们还有 numpy 数组、bytearrayarray.array ...(我还在我的答案中添加了一些时间。)

标签: python dictionary


【解决方案1】:

您可以使用元组作为字典键:

data = { 
    (24,48,96): ["QN.FN.EQ", "OT.AR.LN", "BL.VL.TR"] 
}

print data[(24,48,96)]

【讨论】:

  • 不错,绝对是我需要的!
【解决方案2】:

我正在回答这篇文章标题中的问题。 :)

因为列表是可变的,字典键需要是可散列的,而散列可变对象是个坏主意,因为散列值应该根据实例属性计算。

示例 1:对可变对象进行哈希处理,其中哈希值基于对象的可变特征。

>>> class stupidlist(list):
...     def __hash__(self):
...         return len(self)
... 
>>> stupid = stupidlist([1, 2, 3])
>>> d = {stupid: 0}
>>> stupid.append(4)
>>> stupid
[1, 2, 3, 4]
>>> d
{[1, 2, 3, 4]: 0}
>>> stupid in d
False
>>> stupid in d.keys()
False
>>> stupid in list(d.keys())
True

修改stupid 后,由于哈希值发生了变化,在字典中再也找不到它了。只有对字典的键列表进行线性扫描才能找到stupid

示例 2:...但为什么不只是一个常量哈希值?

>>> class stupidlist2(list):
...     def __hash__(self):
...         return id(self)
... 
>>> stupidA = stupidlist2([1, 2, 3])
>>> stupidB = stupidlist2([1, 2, 3])
>>> 
>>> stupidA == stupidB
True
>>> stupidA in {stupidB: 0}
False

这也不是一个好主意,因为相等的对象应该具有相同的哈希值,以便您可以在 dictset 中找到它们。

示例 3:...好吧,所有实例的常量哈希值如何?!

>>> class stupidlist3(list):
...     def __hash__(self):
...         return 1
... 
>>> stupidC = stupidlist3([1, 2, 3])
>>> stupidD = stupidlist3([1, 2, 3])
>>> stupidE = stupidlist3([1, 2, 3, 4])
>>> 
>>> stupidC in {stupidD: 0}
True
>>> stupidC in {stupidE: 0}
False
>>> d = {stupidC: 0}
>>> stupidC.append(5)
>>> stupidC in d
True

事情似乎按预期工作,但想想发生了什么:当你的类的所有实例产生相同的散列值时,只要有两个以上的实例作为 dict 中的键或存在,你就会发生散列冲突在set

使用d[key]key in d 查找正确的实例需要执行与字典键中stupidlist3 实例一样多的相等检查。在这一点上,字典的目的——O(1) 查找——完全失败了。这在以下时序中得到了证明(使用 IPython 完成)。

一些时间安排

>>> lists_list = [[i]  for i in range(1000)]
>>> stupidlists_set = {stupidlist3([i]) for i in range(1000)}
>>> tuples_set = {(i,) for i in range(1000)}
>>> l = [999]
>>> s = stupidlist3([999])
>>> t = (999,)
>>> 
>>> %timeit l in lists_list
25.5 µs ± 442 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit s in stupidlists_set
38.5 µs ± 61.2 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit t in tuples_set
77.6 ns ± 1.5 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

如您所见,stupidlists_set 中的成员资格测试甚至比对整个 lists_list 的线性扫描还要慢,而您在没有大量哈希的集合中具有预期的超快查找时间(因子 500)碰撞。


TL; DR:你可以使用tuple(yourlist) 作为dict 键,因为元组是不可变的和可散列的。

【讨论】:

  • 为什么最近删了?我刚刚在看书,很高兴你把它找回来了。
  • @BladeMight 我认为这可能是矫枉过正,但后来我改变了主意。 :)
【解决方案3】:

为什么 Python 字典不能接受数组作为 dict 键?

答案:因为数组是python中的一个列表,它是可变的。可变事物不能在 python 中用作字典键。您只能使用 不可变 之类的字符串或元组作为键。

【讨论】:

    【解决方案4】:

    我认为您不允许在 Python 中使用可变数据类型作为键。这就是为什么元组可以工作,而列表却不行。本质上,如果您可以就地更改数据,则不能将其用作键。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-16
      • 1970-01-01
      • 2012-04-18
      • 2013-08-31
      • 1970-01-01
      相关资源
      最近更新 更多