有图有真相之Python拷贝和赋值
Python 是个优化性能极高的语言,共享引用是体现之一。赋值、浅拷贝、深拷贝究竟怎么理解呢?接下来一探究竟!
1. 赋值
惊雷1:对象赋值实际上是对象的引用。原始对象更改,则两个都会发生相同的改变。
a = 3,变量a引用了对象3,对象结构如下图所示。每个对象包含两个头部信息,一个类型标志符,另一个引用计数器。类型标志符标记了这个对象的类型,计数器记录了当前指向该对象的引用的数目,一旦这个计算器被设置为0(即这个对象没有被任何变量引用),该对象的内存空间就会自动回收。
python代码示例:
>>> a = [1,2,[3,4]]
>>> b = a #共享引用对象[1,2,[3,4]]
>>> b is a #验证对象的同一性
True
>>> a.append(5) #新增元素,原对象改变,a变b变
>>> a
[1, 2, [3, 4], 5]
>>> b
[1, 2, [3, 4], 5]
>>> b[3] = 6 #修改元素,原对象改变,b变a变
>>> b
[1, 2, [3, 4], 6]
>>> a
[1, 2, [3, 4], 6]| |
图1 赋值
2. 浅拷贝
惊雷2:浅拷贝copy(),除子对象外的拷贝,只有原子对象改变时,被复制的对象才会改变。通俗点理解:拷贝即复制出来一份,“浅”即“表面的”对象才被独立复制出来一份,但子对象不会复制,仍然共享引用。
>>> import copy
>>> a = [1,2,[3,4]]
>>> b = a.copy() #没有拷贝子对象,公用同一个可变list对象
>>> c = copy.copy(a) #没有拷贝子对象,公用同一个可变list对象
>>> b
[1, 2, [3, 4]]
>>> c
[1, 2, [3, 4]]
>>> b is c #两份对象
False
>>> a.append(5) #未改变子对象[3,4]
>>> a
[1, 2, [3, 4], 5]
>>> b
[1, 2, [3, 4]]
>>> a[2].append(6) #[1, 2, [3, 4]]里面的子对象发生改变
>>> a
[1, 2, [3, 4, 6], 5]
>>> b
[1, 2, [3, 4, 6]]
>>> c
[1, 2, [3, 4, 6]] #说明a.copy() 等同于copy.copy(a),浅拷贝的两种调用形式
图2 浅拷贝
3. 深拷贝
惊雷3:深拷贝deepcopy(),子对象也被拷贝,原对象的改变不会影响被拷贝对象。通俗点理解:深拷贝就是把原来的对象完整的拷贝一份。
>>> import copy
>>> a = [1,2,[3,4]]
>>> b = copy.deepcopy(a) #a和b是完全独立的对象
>>> a.append(5)
>>> a
[1, 2, [3, 4], 5]
>>> b #[1,2,[3,4]]始终未变
[1, 2, [3, 4]]
>>> a[2].append(6)
>>> a
[1, 2, [3, 4, 6], 5]
>>> b #[1,2,[3,4]]始终未变
[1, 2, [3, 4]]
图3 深拷贝
打个比喻,D盘有个子文件夹影视,影视下有能直接打开的视频文件,还有若干个子文件夹,如动作片,历史片,科幻片等,存放分好类的视频。深拷贝就是你鼠标右击复制文件夹影视,然后粘贴到另一个盘,结果当然是所有的都复制出来了;浅拷贝就是操作后只有那些视频文件被粘贴出来了,而子文件夹没被复制上;赋值类似于把文件夹影视网络共享给他人,且他人有所有操作权。