赋值、浅拷贝、深拷贝之间关系的讨论,首先应该从理解Python对数据的存储方式开始。
变量存储的方式:
- 引用语义:变量保存的是对象(值)的引用,采用这种方式下,变量所需的存储空间是一致的。
- 值语义:将变量的值直接保存在变量的存储区内,如C语言,每个变量在内存中所占空间根据变量实际大小而定。
- Python使用的就是第一种——>引用语义
变量初始化对Python中引用的影响
- 变量每次初始化,都开辟新的空间,将新内容地址赋值给变量。如图所示,内存地址发生了改变。

- 数据类型初始化对Python引用的影响

- 数据内部结构改变时(增删改),内存地址没有发生改变,但对该变量进行重新初始化赋值的时候,就给该变量重新赋值了一个地址。
情况1——>简单数据结构中的变量赋值
- 在最开始的赋值中,str1和str2都指向”变量修改前”这个字符串,重新初始化str1后,str1的存储地址发生了改变,指向新建的值,而str2所指向的内存地址并未改变,所以不受影响。

情况2——>复杂数据结构中的变量赋值
- 给列表增删改,我们可以看到列表内数据变了,内存地址并未改变。

深拷贝、浅拷贝的解释:
- 深拷贝:我们寻常意义上的拷贝,就是将被复制对象完全复制一遍,作为独立新个体存在,那么改变被复制对象也就不会改变该新对象了。
- 浅拷贝:并不会产生一个新对象,只是把原有的数据块打上新的标签,所以其中一个标签被改变时,数据块发生变化,另一个标签也就改变。
- 简单而言,就是深拷贝是把数据复制了一遍,换了个地方存起来。浅拷贝只是copy了数据的浅层,并未涉及到数据深层。
情况3——>浅拷贝

这是一个列表,
sourcelist = ['str1','str2','str3','str4','str5',['str1','str2','str3','str4','str5']]

浅拷贝的列表:
copylist = ['str1','str2','str3','str4','str5',['str1','str2','str3','str4','str5']]
看起来和sourcelist一模一样,但其实在内存中已经生成了一个新列表,等于是sourcelist的一份拷贝。

对sourcelist和copylist进行增删操作,发现这两个list在增删父对象元素时,并不会影响对方。 但是当我们修改子对象元素时,却会发现修改list1时,list2也发生了改变:

可以看到,在修改list1中的子列表时,list2对应的子列表也发生了改变。这是因为,在list1中存储的子对象也只是一个“指针”,指向了内存中的该列表对应的内存地址,所以当我们修改“被浅拷贝的对象“、或者“浅拷贝得到的对象”的子对象元素时,要注意,该改变具有联动性。 这也就是为什么我们在用浅拷贝时候,要特别小心的原因。
情况4——>深拷贝
深拷贝就很好理解了,就是在内存中重新开辟一块空间,不管数据结构多么复杂,将其从浅层到最深层全部重新存储,不论数据怎样变动,数据之间的修改都不会影响彼此。

下面贴代码:
import copy
a = [1,2,3,4,["a", "b"]]
# 赋值引用,a和b都指向同一个对象,同一片内存空间——>没有开辟新的内存空间
# 如果对a整体重新赋值,b不变,a变,如果改变a中的一些部分,b跟着a变
b = a
# 拷贝——>开辟了新的内存空间
# 浅拷贝,只会拷贝父对象,不会拷贝父对象中的子对象,而是把子对象当做是一种引用
# 所以如果a中的子对象变则c跟着变
c = copy.copy(a)
# 深拷贝,完全拷贝,完全独立于原对象,a变也不变
d = copy.deepcopy(a)
a.append(5)
a[4].append("c")
最后附上参考链接:
http://www.runstone.top/blog/82/www.runstone.top