在Python字典中赋值(拷贝与引用)
我明白在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 个回答
这是因为 +=
可以理解为 a = a + 1
,这意味着把变量 a
重新指向一个新值,也就是 a + 1
的结果,比如说 2
。
类似地,some_var = "new_value"
也是在重新绑定变量,但对象本身并没有改变,所以字典里的键值对仍然指向那个对象。
在你最后的例子中,你并不是在重新绑定,而是在改变对象的内容,所以字典里的值就发生了变化。
你这里面有两个不同的概念需要搞清楚。第一个是关于可变性和不可变性的。 在Python中,str
(字符串)、int
(整数)、tuple
(元组)是一些内置的不可变类型,而list
(列表)、dict
(字典)等则是可变类型。不可变的对象一旦创建就不能再改变。所以,在你的例子中:
a = b = c = 1
在那行代码之后,所有的a
、b
和c
都指向内存中同一个整数(你可以通过打印它们各自的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
。