为什么Python字典在这个脚本中不独立对待键?
我希望我的烦恼能被一些启发所取代——下面是一个简化版的脚本,用来展示我遇到的问题:
首先,我创建了一个字典:
dic = {
'foo':{},
'bar':{}
}
接着,我们创建一个模板字典,可以逐步添加到 dic
的键中:
appendic= {
'is':'', # '' is a terminal value to be replaced later
}
所以在这里,我们把 appendic
添加到 dic
中每个键的后面:
dic['foo'] = appendic
dic['bar'] = appendic
现在,我们把最后的值 '' 替换成一些有意义的内容:
dic['foo']['is'] = 'foo'
dic['bar']['is'] = 'bar'
到这个时候,我的直觉告诉我,如果我们调用:
print(dic['foo']['is'])
,应该会得到 'foo'
但是实际上,Python 返回的是 'bar'
……对我这个没有经验的人来说,这实在是让人困惑。
问题:
- 我该如何告诉 Python 让
dic
的键保持独立? - 为什么这是默认的行为?这种情况有什么用处?
4 个回答
dic['please_make_me_Foo']= appendic
dic['dont_make_him_Bar'] = appendic
dic['please_make_me_Foo']= appendic.copy()
dic['dont_make_him_Bar'] = appendic.copy()
appendic
是一个对象 - 你把同一个对象的引用赋值给了 dic
中的两个键。所以当你改变一个的时候,另一个也会跟着改变。
试试这个:
好的,我来补充一下其他答案的内容。当你操作一个字典时,其实是在操作一个实例的引用,这就是你出错的根本原因。通过使用 hex(id(foo))
,你可以得到 foo
的内存地址。接下来,我们用一个例子来展示 d
实例的地址,让这个概念更容易理解:
>>> hex(id(d))
'0x10bd95e60'
>>> hex(id(e[1]))
'0x10bd95e60'
>>> hex(id(f[1]))
'0x10bd95e60'
所以,当你在 e[1]
中添加或删除值时,其实是在改变和 d
指向的同一个实例。因为字典是可变的,也就是说,你可以在里面更改值。
你可能会问,为什么处理整数时不会出现这种情况?其实,整数也是会发生这种情况,只是整数是不可变的:
>>> i = 1
>>> hex(id(i))
'0x10ba51e90'
>>> j = i
>>> hex(id(j))
'0x10ba51e90'
>>> i = 2
>>> hex(id(i))
'0x10ba51eb0'
也就是说,i
指向了内存中的另一个地方。
不过,你可以通过使用一个类来创建一个可变的整数:
>>> class Integer:
... def __init__(self, i):
... self.i = i
...
>>> i = Integer(2)
>>> hex(id(i))
'0x10bd9b410'
>>> j = i
>>> hex(id(j))
'0x10bd9b410'
>>> j.i = 2
>>> i.i
2
>>> hex(id(i))
'0x10bd9b410'
要创建一个新的字典实例,你需要使用字典的 copy()
方法:
>>> hex(id(d))
'0x10bd95e60'
>>> w = d.copy()
>>> x = d.copy()
>>> y = d.copy()
>>> hex(id(w))
'0x10bd96128'
>>> hex(id(x))
'0x10bd95f80'
>>> hex(id(y))
'0x10bd96098'
你创建了一个字典:
appendic= {
'Python_made_me':''
}
然后把它添加到另一个字典里两次
dic['please_make_me_Foo']= appendic
dic['dont_make_him_Bar'] = appendic
并把这个字典的 Python_made_me
值设置为两次
dic['please_make_me_Foo']['Python_made_me'] = 'Foo'
dic['dont_make_him_Bar']['Python_made_me'] = 'Bar'
但是因为它们是同一个字典,所以第二次的操作会覆盖第一次的
如果你需要复制这个字典,你需要使用 copy
方法:
dic['please_make_me_Foo']= appendic.copy()
dic['dont_make_him_Bar'] = appendic.copy()
当你把一个叫做 appendic
的东西赋值给两个不同的键时,Python 并不会复制这个东西,而是给它们都指向同一个地方。
所以,dic['please_make_me_Foo']
和 dic['dont_make_him_Bar']
实际上指向的是 同一个对象。它们不是两个不同的字典,而是同一个对象,appendic
也指向这个对象。
如果你希望它们是两个独立的字典,那就需要先复制 appendic
。可以使用 dict.copy()
方法来创建一个字典的浅拷贝:
dic['please_make_me_Foo']= appendic.copy()
dic['dont_make_him_Bar'] = appendic.copy()
浅拷贝的意思是会创建一个新的字典,并把原字典里的键和值的引用都复制过去。
不过,如果 appendic
里面的值本身也是字典,这些值就不会被复制。新的拷贝和 appendic
还是会指向同样的值。在大多数情况下,这并不是问题,因为大部分基本数据类型(比如字符串、整数等)是不可变的,所以当你用新值替换这些值时,你不会注意到它们是共享的引用。