知乎上有同学提了个非常好的问题,这是很多初学者都会碰到的问题。本文从基础知识讲起,来回答一下这个问题。
问题的原文是这样的
l = []
a = {"num": 0}
for i in range(10):
a["num"] = i
l.append(a)
print(l)
循环第一遍 i = 0 这个时候走 l.append(a),然后 l 里面是 [{"num":0}]
走第二遍 i = 1 为什么到第二遍第三步也就是 a["num'] = i 这里,还没有经过l.append(a),l就变成l = [{"num":1}] ?
这个问题其实是很多初学者都会犯的错误,关键是基本概念没有理解透彻。
你以为经过代码的处理,l 会变成这样
但事实上,l 变成了这样
要理解为什么会这样,就必须理解一个关键的概念:引用
什么是引用
引用就是给中的对象起名字。当我们创建一个变量的时候,这个变量里存的就是一个引用,实际指向的是对象,对象是存在内存中的一块数据。
举个例子
a = 100
这段代码,Python 会在内存中创建一个数值为100的 int 类型的对象,变量 a 是一个到该对象内存地址的引用,如下图所示
我们可以通过 id 方法来获取对象在内存中存储的位置,如下
print(id(a))
输出
140438543861200
这个地址就是上图中我们看到的对象存储的位置。
我们再来创建一个变量 b,让它等于 a,如下
b = a
现在打印 b 的地址,看看会输出什么
print(id(b))
输出
140438543861200
可以看到这个结果和上面 a 的地址一模一样。
为什么会这样呢?
原因就在于,变量 b 存的是一个引用,指向的是一个对象,这个对象就是 a 所指向的对象,所以 b 和 a 其实指向了同一个对象,如下图所示
接下来我们改变一下 a 的值
a = 200
再来打印一下 a 的地址
print(id(a))
输出
140438543864464
看到 a 的地址发生了变化。
为什么 a 的地址会发生变化呢?
因为这里我们创建了一个新的对象,这个对象是一个数值为200的 int 类型对象,它的地址不同于上面创建的第一个对象(数值为100的 int 对象)。现在的 a 指向的是这个新的对象,如下
Python 中任何类型的对象,当它赋值给变量的时候,变量里存的都是引用。这也意味着,我们在读取变量的时候,实际读取的是变量所指向的对象的内容。
理解了上面的内容之后,我们再回到最初的问题。
为什么会输出9个9 看一下问题中的代码
l = []
a = {"num": 0}
for i in range(10):
a["num"] = i
l.append(a)
a = {"num": 0} 实际是创建了一个到 {"num": 0} 的引用,如下
a 指向的是存储于 140438724826176 的一个字典。
在 for 了,每次改变 a["num"] = i,其实是改变的同一个字典的 num 这个 key 对应的值。
我们把循环展开,对于每个 i,a 指向的字典的变化情况如下
一直到
这个过程中,a 始终指向同一个字典,只是字典中的值从0变化到了9。
所以每次循环中 , l.append(a) 都是把同一个对象的引用 append 到了 l 中。循环结束,l 就变成了这样
上面的 a 是同一个引用,指向的都是同一个对象。
那循环结束,a 指向的对象的内容是什么呢?
就是上面 i = 9 这张图中的字典,也就是 {"num": 9}。a 指向的地址一直没变,变化的是字典里的内容。
所以现在 l 的内容就是
打印 l 就会看到
[{'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}]
这就是为什么输出9个9。
解决方法
理解了上面的内容,应该也不难想到解决的办法,就是每次循环,都创建一个新的字典,像下面这样
l = []
for i in range(10):
a = {"num": i}
l.append(a)
这里的关键是在循环内部的 a = {"num": i},这样每次循环,a 都指向了一个新的字典。然后 l.append(a) 把这个新的字典添加到 l 中。
循环结束,l 的内容变成了下面这样
l 中的每个元素都是不同的引用,指向的也是不同的对象。我们来验证一下,在循环了把 a 的地址打印出来
l = []
for i in range(10):
a = {"num": i}
print(id(a))
l.append(a)
输出
140438724820416
140438725145088
140438724603968
140438725145472
140438724800256
140438724714688
140438723861696
140438712007936
140438724798656
140438707492096
看到每次地址都不一样。
现在打印 l 就可以得到正确的结果
print(l)
输出
[{'num': 0}, {'num': 1}, {'num': 2}, {'num': 3}, {'num': 4}, {'num': 5}, {'num': 6}, {'num': 7}, {'num': 8}, {'num': 9}]