图解Python深拷贝和浅拷贝
Python中,对象的赋值,拷贝(深/浅拷贝)之间是有差异的,如果使用的时候不注意,就可能产生意外的结果。
下面本文就通过简单的例子介绍一下这些概念之间的差别。
对象赋值
直接看一段代码:
will = ["Will", 28, ["Python", "C#", "JavaScript"]] wilber = will print id(will) print will print [id(ele) for ele in will] print id(wilber) print wilber print [id(ele) for ele in wilber] will[0] = "Wilber" will[2].append("CSS") print id(will) print will print [id(ele) for ele in will] print id(wilber) print wilber print [id(ele) for ele in wilber]
代码的输出为:
下面来分析一下这段代码:
- 首先,创建了一个名为will的变量,这个变量指向一个list对象,从第一张图中可以看到所有对象的地址(每次运行,结果可能不同)
-
然后,通过will变量对wilber变量进行赋值,那么wilber变量将指向will变量对应的对象(内存地址)由于will和wilber指向同一个对象,所以对will的任何修改都会体现在wilber上
-
这里需要注意的一点是,str是不可变类型,所以当修改的时候会替换旧的对象,产生一个新的地址39758496(int也是这种情况)
浅拷贝(等价于切片【:】)
下面就来看看浅拷贝的结果:
import copy will = ["Will", 28, ["Python", "C#", "JavaScript"]] wilber = copy.copy(will) print id(will) print will print [id(ele) for ele in will] print id(wilber) print wilber print [id(ele) for ele in wilber] will[0] = "Wilber" will[2].append("CSS") print id(will) print will print [id(ele) for ele in will] print id(wilber) print wilber print [id(ele) for ele in wilber]
代码结果为:
分析一下这段代码:
通过copy模块里面的浅拷贝函数copy(),对will指向的对象进行浅拷贝,然后浅拷贝生成的新对象赋值给wilber变量
当对will进行修改的时候,两个列表指向不同的地址,但两个列表中嵌套的列表却是相同的类表。修改其中一个列表不会影响另一个列表,但修改一个列表中嵌套的那个列表是就会使另一个发生改变
总结一下,当我们使用下面的操作的时候,会产生浅拷贝的效果:
- 使用切片[:]操作
- 使用工厂函数(如list/dir/set)
- 使用copy模块中的copy()函数
深拷贝
最后来看看深拷贝:
import copy will = ["Will", 28, ["Python", "C#", "JavaScript"]] wilber = copy.deepcopy(will) print id(will) print will print [id(ele) for ele in will] print id(wilber) print wilber print [id(ele) for ele in wilber] will[0] = "Wilber" will[2].append("CSS") print id(will) print will print [id(ele) for ele in will] print id(wilber) print wilber print [id(ele) for ele in wilber]
代码的结果为:
分析一下这段代码:
通过copy模块里面的深拷贝函数deepcopy(),对will指向的对象进行深拷贝,然后深拷贝生成的新对象赋值给wilber变量
当对进行修改的时,两个类表指向及其里面的元素指向不同的地址,对任何一个列表的操作不会影响另一个。
(但是int那个28不是指向相同的地址吗,这个见拷贝的特殊情况,但是修改任意一个的值不会影响另一个)
拷贝的特殊情况
对于非容器类型(如数字、字符串、和其他'原子'类型的对象)没有拷贝这一说,即若两个变量的值相同,就是指向相同的地址
程序(int):
x=5
y=5
z=6
print(id(x),id(y),id(z))
print(x==y)
print(x is y)
x=6
print(id(x),id(y),id(z))
x=7
print(id(x),id(y),id(z))
输出:
(34319432, 34319432, 34319420)
True
True
(34319420, 34319432, 34319420)
(34319408, 34319432, 34319420)
解释:执行x=5开辟了内存空间并填入5,然后执行y=5时就去表里(编译原理的知识)查找,发现有5这个常量,就指向x对应的空间,在之后执行z=6时,先去表里查询发现没有6,就新开辟空间。然后x=6,表里有6这个常量,就去指向z对应的空间,再次修改x=7时,因为表里没有7这个常量,就新开辟内存空间,并把7填入该空间,然后令x指向该空间
即x为原子变量,每次执行x=时都从新分配空间,这个区别于非原子变量
-
只包含原子类型(int,float,str)对象,则不能深拷贝,看下面的例子
总结
本文介绍了对象的赋值和拷贝,以及它们之间的差异:
- Python中对象的赋值都是进行对象引用(内存地址)传递
- 使用copy.copy(),可以进行对象的浅拷贝,它复制了对象,但对于对象中的元素,依然使用原始的引用.
- 如果需要复制一个容器对象,以及它里面的所有元素(包含元素的子元素),可以使用copy.deepcopy()进行深拷贝
- 对于非容器类型(如数字、字符串、和其他'原子'类型的对象)没有被拷贝一说
-
作者:田小计划
出处:http://www.cnblogs.com/wilber2013/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。