其实文章的标题严谨性有所欠缺,虽然底层是用C语言实现的,但是python语言没有指针这一说。这么取名是为了和我们学过的内容相互联系类比,从而获得更好的理解。
在Python中,有时候想当然认为使用等号赋值如
b = [1, 2, 3, 4]
a = b
可以获得一个变量的副本,其实并不如此。实际上此时的a
获得的是b
的一个引用,如下图所示:
可以看到,a和b指向的是同一个内容,也即此时的等号赋值并不是复制,而是传递引用。两者指向同一个对象。
这个问题在很多场合都没有问题(例如遍历a和遍历b),只是相当于换了一个名字而已,用a和b都没有任何区别。
但是当到了需要对数据操作的时候(例如增加数据,删除数据)的时候就会发生问题。
给定如下程序,试分析其输出:
a = {'a': 1}
c = []
for i in range(3):
b = a
b['b'] = i
c.append(b)
print(c)
第一直觉分析,输出列表c包含3个字典,每个字典包含2个项,如下所示:
[{'a': 1, 'b': 0}, {'a': 1, 'b': 1}, {'a': 1, 'b': 2}]
然而实际输出为:
[{'a': 1, 'b': 2}, {'a': 1, 'b': 2}, {'a': 1, 'b': 2}]
为什么其中的每个项都是一样的?
错误在于使用了b = a
对a进行复制,该语句并不会对a进行复制,而是生成一个a的引用。
如上图所示,每次使用b = a
时,都只是生成了字典a的引用,每次append到列表c中的都是字典a的引用,所以每次修改a都对列表c中的所有项生效,经过3次循环,最后一次修改字典a得到的是{'a': 1, 'b': 2}
,于是列表c中的每一项都是一样的。
可以在以上程序再做进一步修改:
a = {'a': 1}
c = []
for i in range(3):
b = a
b['b'] = i
c.append(b)
a['a'] = 666
a['c'] = 888
print(c)
输出为:
[{'a': 666, 'b': 2, 'c': 888}, {'a': 666, 'b': 2, 'c': 888}, {'a': 666, 'b': 2, 'c': 888}]
可以看到每次对a的修改都会作用到列表c中的每个元素,因为列表c中存储的并不是具体的值,而是一个引用(指针),他们都指向同一个位置,牵一发而动全身。
如何修改才能正常?
经过上面分析,只需要每次append到列表c中的不是同一个内容即可,我们使用copy方法获取a的副本,从而每次append到列表c中的内容都是一个新的值,如下所示:
a = {'a': 1}
c = []
for i in range(3):
b = a.copy()
b['b'] = i
c.append(b)
print(c)
输出结果:
[{'a': 1, 'b': 0}, {'a': 1, 'b': 1}, {'a': 1, 'b': 2}]
可以看到这一次的结果如同预期。
动态演示上面两个代码的区别:
- 未添加copy方法
- 添加copy方法
文章中可视化执行python代码网站地址:链接