在Python字典中赋值(拷贝与引用)

21 投票
2 回答
35462 浏览
提问于 2025-04-18 07:31

我明白在Python中,所有东西,无论是数字、字符串、字典还是其他,都是对象。变量名只是指向内存中的对象。根据这个问题

>> a_dict = b_dict = c_dict = {}

这行代码创建了一个空字典,所有的变量都指向这个字典对象。因此,修改其中一个变量的内容会在其他变量中反映出来。

>> a_dict["key"] = "value" #say
>> print a_dict
>> print b_dict
>> print c_dict

会得到

{'key': value}
{'key': value}
{'key': value}

我理解变量指向对象的概念,所以这看起来是合理的。

不过,尽管这可能有点奇怪,因为这是个基本的说法,为什么会这样呢?

>> a = b = c = 1
>> a += 1
>> print a, b, c
2, 1, 1   # and not 2, 2, 2

问题的第一部分: 为什么这里没有应用相同的概念?

其实这个疑问是在我寻找这个问题的解决方案时产生的:

>> a_dict = {}
>> some_var = "old_value"
>> a_dict['key'] = some_var
>> some_var = "new_value"
>> print a_dict
{'key': 'old_value'}  # and not {'key': 'new_value'}

这让我觉得有点不符合直觉,因为我一直以为我是告诉字典去保存这个变量,而改变变量所指向的对象显然会在字典中反映出来。但这让我觉得好像是值被复制了,而不是引用。这是我不理解的第二件事。

接下来,我尝试了其他方法

>> class some_class(object):
..    def __init__(self):
..        self.var = "old_value"
>> some_object = some_class()
>> a_dict = {}
>> a_dict['key'] = some_object
>> some_object.var = "new_value"
>> print a_dict['key'].var
"new_value"    # even though this was what i wanted and expected, it conflicts with the output in the previous code

在这里,显然是被引用了。这些矛盾让我对Python不可预测的特性感到困惑,尽管我仍然喜欢它,因为我对其他语言了解得不够深入 :p。虽然我一直想象赋值会导致对对象的引用,但这两个情况却是矛盾的。所以这是我最后的疑问。 我明白这可能是Python中的一个陷阱。请教教我。

2 个回答

5

这是因为 += 可以理解为 a = a + 1,这意味着把变量 a 重新指向一个新值,也就是 a + 1 的结果,比如说 2

类似地,some_var = "new_value" 也是在重新绑定变量,但对象本身并没有改变,所以字典里的键值对仍然指向那个对象。

在你最后的例子中,你并不是在重新绑定,而是在改变对象的内容,所以字典里的值就发生了变化。

26

你这里面有两个不同的概念需要搞清楚。第一个是关于可变性不可变性的。 在Python中,str(字符串)、int(整数)、tuple(元组)是一些内置的不可变类型,而list(列表)、dict(字典)等则是可变类型。不可变的对象一旦创建就不能再改变。所以,在你的例子中:

a = b = c = 1

在那行代码之后,所有的abc都指向内存中同一个整数(你可以通过打印它们各自的id来检查,发现它们是一样的)。但是,当你执行:

a += 1

此时,a指向了一个新的(不同的)整数,存储在不同的内存位置。需要注意的是,通常情况下,如果类型是不可变的,+=应该返回一个新的实例。如果类型是可变的,它应该在原地改变对象并返回它。我在这个回答中解释了一些更详细的内容。


第二部分,你在试图弄清楚Python的标识符是如何工作的。我理解的方式是这样的……当你写一条语句:

name = something

右边的部分会被计算成某个对象(比如一个整数、字符串等)。然后这个对象会被赋予左边的名字1。当一个名字出现在右边时,系统会自动“查找”对应的对象,并在计算中用这个对象替代名字。需要注意的是,在这个框架下,赋值操作并不关心之前是否有其他东西使用过这个名字——它只是简单地用新值覆盖旧值。之前用这个名字构造的对象不会看到任何变化——它们已经被创建了——保持的是对对象本身的引用,而不是名字。所以:

a = "foo"  # `a` is the name of the string "foo" 
b = {"bar": a}  # evaluate the new dictionary and name it `b`.  `a` is looked up and returns "foo" in this calculation
a = "bar"  # give the object "bar" the name `a` irrespecitve of what previously had that name

1为了简单起见,我这里省略了一些细节——例如,当你给列表元素赋值时会发生什么:lst[idx] = some_value * some_other_value

撰写回答