使用可变对象的副本作为字典键

时间:2015-12-04 14:16:56

标签: python dictionary

据我所知,python需要一个不可变的对象才能用作字典键。例如,我们无法使用list作为字典键,我们必须先将它们转换为tuple。因此,制作可变对象的副本并将副本用作密钥是错误的。这种方式对对象的更改不会影响密钥。是因为这种方法空间效率低下还是别的什么?

3 个答案:

答案 0 :(得分:4)

由于多种原因,

dict不能通过内部制作深层副本来接受可变对象,每个原因都是足够的。其中包括:

1)这会使插入的开销任意高。

2)这会使插入的开销变得不可预测。

3)它会破坏O(1)查找。

O(1)查找是通过散列密钥并使用该散列值作为表的索引来实现的。这预先假定密钥可以存在永久哈希。

在您的假设版本的Python中考虑这个程序:

d = { [1,2,3]:"hello" }
for k in d:
    k.append(4)

我们正在修改dict的复制密钥对象。显然必须重新计算哈希值,同样显然必须重新计算d的哈希表。但当?或者,为了拟人化,d如何知道k修改了自己?答案:实际上,它不能。

不,如果我们想要O(1)查找,我们需要一个哈希表。如果我们想要一个哈希表,我们需要密钥的永久哈希值。如果我们想要键的永久哈希值,我们需要不可变对象。

相反,如果我们想要可变密钥,我们可以实现O(logN)查找。但由于Python需要O(1)查找,我们有不可变的键。

答案 1 :(得分:1)

我认为一个例子将有助于理解:

my_value = 123456
my_string = 'key'

my_dict = {my_string : my_value}

my_string = 'not key anymore!'

print my_dict['key'] #prints : 123456
print my_dict[my_string] # raise KeyError

正如您所看到的,在修改“原始”对象后仍可以重新启用密钥。但我说“原始”,因为对象没有真正修改,但创建了一个新对象。那是不可改变的意思。 你可以在这里阅读:https://docs.python.org/2/reference/datamodel.html

如果密钥是可变的,那么当我更改对象本身时,密钥的值也会改变。

答案 2 :(得分:1)

因为Python不喜欢制作太多内容。将(key,value)项添加到dict时,dictionnary中的键和值引用到原始对象。

即使密钥是原始对象的副本,您仍然可以通过迭代items来获取对它的引用。一旦你得到了这个参考,你就可以用所有相关的问题来改变它......

因此,复制方法要求dict不仅在添加元素时获取密钥的副本,而且还始终在keys()items()方法中返回新副本

最后但并非最不重要的是,由于存在可变类和非可变类,因此可以在Python中构建不可复制的类:

>>> class UnCopy(object):
    def __new__(cls, x, y):
        obj = super(UnCopy, cls).__new__(cls)
        obj.x = x
        obj.y = y
        return obj


>>> c = UnCopy('a', 'b')
>>> d = copy.deepcopy(c)

Traceback (most recent call last):
  File "<pyshell#20>", line 1, in <module>
    d = copy.deepcopy(c)
  File "C:\Python27\lib\copy.py", line 190, in deepcopy
    y = _reconstruct(x, rv, 1, memo)
  File "C:\Python27\lib\copy.py", line 329, in _reconstruct
    y = callable(*args)
  File "C:\Python27\lib\copy_reg.py", line 93, in __newobj__
    return cls.__new__(cls, *args)
TypeError: __new__() takes exactly 3 arguments (1 given)

无论如何,唯一真正的原因是:它不是dict类的行为方式,而是来自collection模块的其他映射类。