为什么Python字典在这个脚本中不独立对待键?

0 投票
4 回答
611 浏览
提问于 2025-04-18 05:51

我希望我的烦恼能被一些启发所取代——下面是一个简化版的脚本,用来展示我遇到的问题:

首先,我创建了一个字典:

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 个回答

2
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 中的两个键。所以当你改变一个的时候,另一个也会跟着改变。

试试这个:

3

好的,我来补充一下其他答案的内容。当你操作一个字典时,其实是在操作一个实例的引用,这就是你出错的根本原因。通过使用 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'
4

你创建了一个字典:

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()
9

当你把一个叫做 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 还是会指向同样的值。在大多数情况下,这并不是问题,因为大部分基本数据类型(比如字符串、整数等)是不可变的,所以当你用新值替换这些值时,你不会注意到它们是共享的引用。

撰写回答