这个python代码为什么输出9个9?

知乎上有同学提了个非常好的问题,这是很多初学者都会碰到的问题。本文从基础知识讲起,来回答一下这个问题。

问题的原文是这样的

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}]