Python中的浅拷贝以及深拷贝

  • Python中数据类型
  • 不可变数据对象
  • 可变数据类型
  • 代码表示
  • 浅拷贝与深拷贝
  • 代码表示


Python中数据类型

不可变数据对象

不可变指的是数据存储到内存后无法进行修改。
python中的不可变数据类型有:int、float、str、tuple、bool

可变数据类型

可变指的是数据存储后可以进行修改。
python中的可变数据类型有:list、dict、set

代码表示

>>>print('修改前:')
>>>i = 1
>>>f = 5.2
>>>s = 'hello'
>>>t = (1,2,3)
>>>b = True
>>>print('i={},id(i)={}\nf={},id(f)={}\ns={}id(s)={}\nt={},id(t)={}\nb={},id(b)={}\n'.format(i,id(i),f,id(f),s,id(s),t,id(t),b,id(b)))
>>>print('修改后:')
>>>i = 2
>>>f = 5.3
>>>s = 'helloWorld'
>>>t = (4,5,6)
>>>b = False
>>>print('i={},id(i)={}\nf={},id(f)={}\ns={}id(s)={}\nt={},id(t)={}\nb={},id(b)={}\n'.format(i,id(i),f,id(f),s,id(s),t,id(t),b,id(b)))

输出结果
修改前:
i=1,id(i)=1596320880
f=5.2,id(f)=2558329694656
s=helloid(s)=2558344746464
t=(1, 2, 3),id(t)=2558344774928
b=True,id(b)=1596063952
修改后:
i=2,id(i)=1596320912
f=5.3,id(f)=2558329694920
s=helloWorldid(s)=2558340497008
t=(4, 5, 6),id(t)=2558344775000
b=False,id(b)=1596063984

结论
对python中不可变数据类型进行重复赋值(修改),实际上是重新将变量名指向了新的内存地址
而对于python中可变数据类型,读者自己动手尝试后也会发现,修改前后变量名所指向的地址是不变的,这里不再赘述。
ps:对于除tuple外的int、float、str、bool四种数据类型,相同的值在python中内存的位置是相同的。

浅拷贝与深拷贝

先明确一点,这里讨论的都是基于可变数据对象的,原因见上文。

根据官方文档对于copy.copy()以及copy.deepcopy()两个方法的描述:

拷贝python包 python怎么拷贝_深拷贝


深浅拷贝间的差异仅仅反应在对于复合对象的拷贝中,即包含其他对象的对象。

其次,官方对于深浅拷贝也有着描述:

拷贝python包 python怎么拷贝_python_02


浅拷贝构造了一个新的复合对象,然后将从从原始对象中找到的引用插入到新对象中。

深拷贝则是将原始对象的副本插入到了新对象中。

简单来说,对于复合对象而言:

浅拷贝后,改变原始对象中可变类型的数据的值,同时影响拷贝对象

深拷贝后,改变原始对象中可变类型的数据的值,不会同时影响拷贝对象。

代码表示

>>>import copy
>>>print('修改前:')
>>> list1 = [[1,2,3],'wx',3]
>>> list2 = list1  # 直接将原列表的引用赋值给list2
>>> list3 = list1[:]  # 切片后赋值给list3,效果与浅拷贝一样
>>> list4 = copy.copy(list1)  # 浅拷贝
>>> list5 = copy.deepcopy(list1)  # 深拷贝
>>>print('list1={},id(list1)={},id(list1[0])={}\nlist2={},id(list2)={},id(list2[0])={}\nlist3={},id(list3)={},id(list3[0])={}\nlist4={},id(list4)={},id(list4[0])={}\nlist5={},id(list5)={},id(list5[0])={}\n'.format(list1,id(list1),id(list1[0]),list2,id(list2),id(list2[0]),list3,id(list3),id(list3[0]),list4,id(list4),id(list4[0]),list5,id(list5),id(list5[0])))
>>>print('修改后:')
>>>list1.append(6)
>>> list1[0].append(4)
>>> print('list1={},id(list1)={},id(list1[0])={}\nlist2={},id(list2)={},id(list2[0])={}\nlist3={},id(list3)={},id(list3[0])={}\nlist4={},id(list4)={},id(list4[0])={}\nlist5={},id(list5)={},id(list5[0])={}\n'.format(list1,id(list1),id(list1[0]),list2,id(list2),id(list2[0]),list3,id(list3),id(list3[0]),list4,id(list4),id(list4[0]),list5,id(list5),id(list5[0])))

输出结果
修改前:
list1=[[1, 2, 3], ‘wx’, 3],id(list1)=1778195532040,id(list1[0])=1778195665224
list2=[[1, 2, 3], ‘wx’, 3],id(list2)=1778195532040,id(list2[0])=1778195665224
list3=[[1, 2, 3], ‘wx’, 3],id(list3)=1778195665032,id(list3[0])=1778195665224
list4=[[1, 2, 3], ‘wx’, 3],id(list4)=1778195586696,id(list4[0])=1778195665224
list5=[[1, 2, 3], ‘wx’, 3],id(list5)=1778195586760,id(list5[0])=1778195586888
修改后:
list1=[[1, 2, 3, 4], ‘wx’, 3, 6],id(list1)=1778195532040,id(list1[0])=1778195665224
list2=[[1, 2, 3, 4], ‘wx’, 3, 6],id(list2)=1778195532040,id(list2[0])=1778195665224
list3=[[1, 2, 3, 4], ‘wx’, 3],id(list3)=1778195665032,id(list3[0])=1778195665224
list4=[[1, 2, 3, 4], ‘wx’, 3],id(list4)=1778195586696,id(list4[0])=1778195665224
list5=[[1, 2, 3], ‘wx’, 3],id(list5)=1778195586760,id(list5[0])=1778195586888

结论
OK,代码很好懂,但是输出结果较多,我们先看修改前输出的数据,根据地址来进行判断。
list2相当于list1的引用,所以无论是浅层数据结构(不可变数据)还是深层数据结构(可变数据)都与list1相同。
list3、list4效果一样,均为list1的浅拷贝,两对象的地址均不同于list1,但当涉及到深层数据结构(可变数据list1[0]为列表)时,实际还是指向了原列表list1.
list5为list1的深拷贝,可以看出无论其本身对象还是其中的深层数据结构(可变数据list1[0]为列表)都为副本
接着是修改后输出的数据。
list1、list2不再赘述,两者不管谁改变,都会互相影响。
深浅拷贝后,均生成了新的对象,所以原对象中的浅层次数据结构(不可变数据)的增加减少不会影响list3、list4,但是对于深层次数据结构(可变数据list1[0]为列表)来说,新列表list3、list4中的相应元素还是指向原列表list1,所以当原列表list1中的list1[0](深层次数据结构)发生改变时,浅拷贝出的list3、list4也会受到影响(反之亦然)。
对于深拷贝生成的list5,无论时深层次还是浅层次都为原对象的副本,所以两者不会互相影响。
简而言之,浅拷贝更像是“藕断丝连”式的拷贝,而深拷贝则是完完全全的“复制粘贴”。

一个好哥们的疑问促使我码出了这篇文章,在解答过程中我也发现了自己的不足,故而将自己的一些见解贴出来,若有不当之处,还望包涵并指正,谢谢。