我的个人网站:https://www.gentlecp.com
一、引用
在python中需理清一个概念,所有代码中呈现的对象,其实是该对象的一个引用,一个对象可以被多次引用,python通过内存回收机制在对象引用次数为0的时候将该对象的内存空间回收,释放内存。看如下代码
# 创建一个列表
a = [1,2,3] # 创建对象[1,2,3]被a引用
# b引用a
b = a # b 通过a间接引用了[1,2,3]
# 修改a引用对象的内容
a[0] = 4
print(a) # [4, 2, 3]
print(b) # [4, 2, 3]
可以发现当a修改之后,b也跟着修改了,因为他们引用的是同一个对象[1,2,3]。可以用sys.getrefcount()方法查看a的引用次数
sys.getrefcount(a) # 3,getrefcount将a作为参数也是一次引用
所以我们想对对象进行复制的时候,不能直接用这种 a = b的方式,这只将b引用给了a。有人可能问了,我用下面的代码,前者改变不影响后者啊
a = 3
b = a
a = 5
print(a) # 5
print(b) # 3
这是因为数字为不可变对象,a = 5 的操作相当于重新创建了一个新的对象赋予a,此时a,b引用的是不同对象。同样的解释也可用在元组,字符串(所有不可变对象)上。
二、拷贝
那当我们想复制对象的时候怎么办(一方改变不影响另一方)。实际上只要实现让他们引用不同对象即可,这里需要分两种情况考虑。
2.1 浅拷贝
浅拷贝可以做到顶层复制,如列表,字典自带的copy()方法,和copy模块的copy方法。看下面代码
# 自带copy()
a = [1,2,3]
b = a.copy()
a[0] = 4
print(a) # [4,2,3]
print(b) # [1,2,3]
# copy模块
import copy
a = [1,2,3]
b = copy.copy(a)
a[0] = 4
print(a) # [4,2,3]
print(b) # [1,2,3]
除了以上copy方法,列表还可以利用切片操作,如下
a = [1,2,3]
b = a[:]
a[0] = 4
print(a) # [4,2,3]
print(b) # [1,2,3]
但前面说了,这种拷贝方式属于浅拷贝,只能拷贝顶层内容,什么意思?看下面的代码
a = [[1,2,3],4]
b = a.copy()
a[0][1] = 5
a[1] = 6
print(a) # [[1, 5, 3], 6]
print(b) # [[1, 5, 3], 4]
这说明b只拷贝了顶层列表,对于内层嵌套的列表元素仍然是引用的方式,这时候就用到了深拷贝
2.2 深拷贝
用copy模块的deepcopy()方法进行深拷贝,相当于将对象的数据结构全部都复制一份,两者将是完全独立的个体,互不影响
import copy
a = [[1,2,3],4]
b = copy.deepcopy(a)
a[0][1] = 5
print(a) # [[1, 5, 3], 4]
print(b) # [[1, 2, 3], 4]
三、总结
引用和复制要根据你自己的需求进行选择,在保证源对象不变的条件下,采用copy的方式。如果源对象的修改对程序没有影响或就需要在源对象上修改则可以采用直接赋值引用。
最后留一个小思考(请自己尝试一下)
a = [1,2,3]
b = a * 3
c = [a] * 3
a[0] = 4
b,c的值会更改吗?