为什么我不能在Python中使用一个列表作为dict键?

131 人关注

我对什么可以/不可以作为python dict的键有点疑惑。

dicked = {}
dicked[None] = 'foo'     # None ok
dicked[(1,3)] = 'baz'    # tuple ok
import sys
dicked[sys] = 'bar'      # wow, even a module is ok !
dicked[(1,[3])] = 'qux'  # oops, not allowed

所以元组是一个不可变的类型,但如果我在其中隐藏一个列表,那么它就不能成为一个键。难道我不能很容易地在一个模块中隐藏一个列表吗?

我有一些模糊的想法,即密钥必须是 "可哈希 "的,但我要承认我自己对技术细节的无知;我不知道这里到底发生了什么。 如果你试图用列表作为键,用哈希值作为,比如说,它们的内存位置,会出什么问题?

2 个评论
这里有一个很好的讨论。 stackoverflow.com/questions/2671211/...
从你的变量名称中得到了一个笑声。
python
list
dictionary
tuples
hashable
wim
wim
发布于 2011-08-31
11 个回答
user395760
发布于 2011-08-31
已采纳
0 人赞同

在Python wiki中,有一篇关于这个主题的好文章。 为什么清单不能成为字典的关键 .正如那里所解释的。

如果你试图用列表作为键,用哈希作为,比如说,他们的内存位置,会出什么问题?

它可以在不真正破坏任何要求的情况下进行,但它会导致意外的行为。列表通常被当作它们的值来自于它们的内容的值,例如在检查(in-)equity时。许多人会--可以理解--期望你可以使用任何列表 [1, 2] 来获得相同的键,在这里你必须保持周围完全相同的列表对象。但是,一旦用作键的列表被修改,按值查找就会中断,而且按身份查找需要你保留完全相同的列表--这对任何其他常见的列表操作来说都是不需要的(至少我想不出来)。

其他对象,如模块和 object ,无论如何都要对它们的对象身份做更大的处理(你最后一次有两个不同的模块对象被称为 sys 是什么时候),并且无论如何都要通过这个来比较。因此,在这种情况下,当它们被用作dict键时,通过身份进行比较也就不那么令人惊讶了,甚至是意料之中。

Remi
Remi
发布于 2011-08-31
0 人赞同

为什么我不能在Python中使用一个列表作为dict键?

>>> d = {repr([1,2,3]): 'value'}
{'[1, 2, 3]': 'value'}

(对于任何偶然发现这个问题并寻找解决方法的人来说)

正如这里的其他人所解释的那样,你确实不能这样做。但是,如果你真的想使用你的列表,你可以使用它的字符串表示法来代替。

wim
对不起,我不太明白你的意思。 这与使用字符串字面作为键没有什么不同。
Remi
是的;我只是看到许多答案实际上是在解释为什么你不能在 "键必须是可散列的 "方面使用列表,这是非常正确的,所以我想提出一个绕过它的方法,以防有人(新)会寻找它。
为什么不直接将列表转换为元组?为什么要把它转换为字符串呢?如果你使用元组,它将与有自定义比较方法的类正确工作 __eq__ 。但是如果你把它们转换为字符串,所有的东西都会被它的字符串表示法所比较。
Remi
说得好 @Aran-Fey.只要确保元组中的任何元素本身是可散列的。例如,元组([[1,2],[2,3]])作为一个键将无法工作,因为元组的元素仍然是列表。
Ningrong Ye
Ningrong Ye
发布于 2011-08-31
0 人赞同

我发现你可以把List改成tuple,然后用它作为key。

d = {tuple([1,2,3]): 'value'}
    
Eric Wilson
Eric Wilson
发布于 2011-08-31
0 人赞同

问题是图元是不可变的,而列表则不是。考虑一下下面的情况

d = {}
li = [1,2,3]
d[li] = 5
li.append(4)

d[li]应该返回什么?是相同的列表吗?那么d[[1,2,3]]呢?它有相同的值,但却是一个不同的列表?

归根结底,没有令人满意的答案。例如,如果唯一有效的键是原始键,那么如果你没有对该键的引用,你就永远无法再次访问该值。对于其他每一个允许的键,你可以构建一个没有对原始键的引用的键。

如果我的两个建议都起作用,那么你有非常不同的键,但返回相同的值,这就有点令人惊讶了。如果只有原来的内容有效,那么你的键会很快变坏,因为列表是用来修改的。

wim
是的,它是同一个列表,所以我希望 d[li] 保持为5。 d[[1,2,3]] 将引用一个不同的列表对象作为键,所以它将是一个 KeyError。 我还没有看到任何问题......除了让一个键被垃圾回收可能使一些 dict 值无法访问。 但这是个实际问题,不是逻辑问题。
@wim:替换代码0】是一个KeyError,是问题的一部分。在几乎 其他每一种使用情况 , li 将与一个内容相同的新列表没有区别。这很有效,但对很多人来说是违反直觉的。另外,你上次是什么时候 真的 必须使用一个列表作为dict key?我能想象到的唯一用例是当你无论如何都要按身份进行散列时,在这种情况下,你应该直接这样做,而不是依靠 __hash__ __eq__ 来实现基于身份。
wim
@delnan 是 problem 还是有什么原因导致它实际上会破坏Dict呢?
@wim:是后者。正如我的回答中所说的,它并没有真正破坏对数字键的要求,但它可能会带来更多的问题而不是解决。
@delnan - 你的意思是说 "前者
bpgergo
bpgergo
发布于 2011-08-31
0 人赞同

这里有一个答案 http://wiki.python.org/moin/DictionaryKeys

如果你试图用列表作为键,用哈希作为,比如说,他们的内存位置,会出什么问题?

查找具有相同内容的不同列表会产生不同的结果,即使比较具有相同内容的列表会显示它们是等价的。

在字典查询中使用一个列表字面是什么情况?

timgeb
timgeb
发布于 2011-08-31
0 人赞同

因为列表是可变的, dict 键(和 set 成员)需要是可散列的,而散列可变对象是一个坏主意,因为散列值 should 是在实例属性的基础上计算出来的。

在这个答案中,我将给出一些具体的例子,希望能在现有答案的基础上增加价值。每一个见解也适用于 set 数据架构的元素。

例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]
{[1, 2, 3, 4]: 0}
>>> stupid in d
False
>>> stupid in d.keys()
False
>>> stupid in list(d.keys())

在突变了stupid之后,由于哈希值的改变,在dict中无法再找到它。只有对dict的键列表进行线性扫描才能找到stupid

例2:......但为什么不只是一个恒定的哈希值?

>>> class stupidlist2(list):
...     def __hash__(self):
...         return id(self)
>>> stupidA = stupidlist2([1, 2, 3])
>>> stupidB = stupidlist2([1, 2, 3])
>>> stupidA == stupidB
>>> 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}
>>> stupidC in {stupidE: 0}
False
>>> d = {stupidC: 0}
>>> stupidC.append(5)
>>> stupidC in d

事情看起来像预期的那样,但想想正在发生的事情:当你的类的所有实例产生相同的哈希值时,只要有两个以上的实例作为dict中的键或存在于set中,你就会发生哈希碰撞。

找到my_dict[key]key in my_dict(或item in my_set)的正确实例,需要执行与dict的键中存在的stupidlist3的实例一样多的平等检查(在最坏情况下)。在这一点上,字典的目的--O(1)查找--被完全击败了。这在下面的计时中得到了证明(用 IPython 完成)。

Some Timings for 例3

>>> 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的键,因为图元是不可变和可散列的。

>>> x=(1,2,3321321321321,) >>> id(x) 139936535758888 >>> z=(1,2,3321321321321,) >>> id(z) 139936535760544 >>> id((1,2,3321321321321,)) 139936535810768 These 3 have same tuple values but different id. So a dictionary with key x won't have any value for key z?
@Ashwani 你试过了吗?
是的,它正在按预期工作,我的疑问是,所有具有相同价值的图元都有不同的ID。那么这个哈希值是根据什么来计算的呢?
@Ashwani x z 的哈希值是一样的。如果有不清楚的地方,请开一个新问题。
@Ashwani hash(x) and hash(z) .
AKjsd89
AKjsd89
发布于 2011-08-31
0 人赞同

你的答案可以在这里找到。

为什么清单不能成为字典的关键

初次接触 Python 的人经常想知道,为什么该语言同时包括 一个元组和一个列表类型,但元组可以作为字典的键,而 列表却不能。这是一个深思熟虑的设计决定,最好的解释是 解释,首先要了解 Python 字典的工作原理。

Source & more info: http://wiki.python.org/moin/DictionaryKeys

Ben Wright
Ben Wright
发布于 2011-08-31
0 人赞同

对你的问题的简单回答是,类列表没有实现方法 散列 这是任何希望被用作字典中的一个键的对象所需要的。然而,为什么 散列 is not implemented the same way it is in say the tuple class (based on the content of the container) is because a list is mutable so editing the list would require the 散列 to be recalculated which may mean the list in now located in the wrong bucket within the underling 散列 table. Note that since you cannot modify a tuple (immutable) it doesn't run into this problem.

顺便提一下,dictobjects查找的实际实现是基于Knuth第三卷第6.4节的算法D。如果你有那本书,也许值得一读,此外,如果你真的非常感兴趣,你可能想看一下实际的开发者评论。 这里是dictobject的实现。 它非常详细地介绍了它的具体运作方式。还有一个 python lecture on the implementation of dictionaries which you may be interested in. They go through the definition of a key and what a 散列 is in the first few minutes.

DARK_C0D3R
DARK_C0D3R
发布于 2011-08-31
0 人赞同

Dictionary是一个HashMap,它存储了你的键和值的映射。 到一个哈希的新键和值的映射。

something like (psuedo code):

{key : val}  
hash(key) = val

如果你想知道哪些是可用的选项,可以作为你的字典的钥匙。那么

任何东西都是hashable(可转换为哈希值,并持有静态值,即不可改变的,以便使一个 如上所述的散列键) is eligible but 由于列表或集合对象可以随时变化,所以hash(key)也需要变化,以便与你的列表或集合保持同步。

你可以尝试:

hash(<your key here>)

如果它工作正常,它可以作为你的字典的键,或者把它转换成可散列的东西。

Inshort :

  • Convert that list to tuple(<your list>).
  • Convert that list to str(<your list>).
  • Viraj Dhanushka
    Viraj Dhanushka
    发布于 2011-08-31
    0 人赞同

    我们可以简单地记住, dict 键需要 不变的 (确切地说。 可散列 ). Lists are mutable(确切地说。lists do not provide a valid __hash__ method).

    Here an 不变的 object (unchangeable object) is an object whose state cannot be modified after it is created. This is in contrast to a mutable object (changeable object), which can be modified after it is created.

    Nicola Musatti
    Nicola Musatti
    发布于 2011-08-31
    0 人赞同

    根据Python 2.7.2文档。

    如果一个对象有一个在其生命周期内永不改变的哈希值,那么它就是可哈希的(它需要一个 __hash__() 方法)。 它需要一个 __hash__() 方法),并且可以与其他对象进行比较(它需要一个 __eq__() __cmp__() 方法)。 与其他对象进行比较(它需要一个 __eq__() __cmp__() 方法)。 比较相等的哈希对象必须有相同的哈希值。

    散列性使一个对象可以作为字典的键和集合的 成员,因为这些数据结构在内部使用哈希值。

    所有 Python 的不可变的内置对象都是可散列的,而没有一个 可变容器 (如列表或字典) 是可散列的。对象