本章节内容涉及可变类型和不可变类型的基础知识,详见以下文章: https://blog.csdn.net/matlab2007/article/details/102831025
准则:不可变类型变量的复制,一个变,另外一个永远不跟着变。
import copy a = 'hello world' b = a c = copy.copy(a) d = copy.deepcopy(a) print(id(a),id(b),id(c),id(d))运行结果: 322518536 322518536 322518536 322518536 copy和deepcopy的作用没有区别。语句b = a执行后,虽然a和b的值和id一样,但将来修改其中任何一个,另外一个均不受影响。如果该处不明白,请参考可变类型和不可变类型的基础知识。
即可变类型的子对象为数字、字符串、布尔值等不可变类型组成,不包含列表、字典、集合等可变类型复杂子对象。
初始化并进行复制:
a = [100,200,300] b = a c = copy.copy(a) d = copy.deepcopy(a) print(id(a),id(b),id(c),id(d))运行结果: 323457272 323457272 322518112 322517352 上面的代码运行结果可以看出,copy和deepcopy复制之后的变量id一致均与原id不一样。下面测一下子对象的id值。
a = [100,200,300] b = a c = copy.copy(a) d = copy.deepcopy(a) print(id(a[0]),id(b[0]),id(c[0]),id(d[0]))运行结果:505988168 505988168 505988168 505988168 运行结果可能超出想象,a中不可变的子对象“100”,历经赋值、浅拷贝、深拷贝后,竟然都是指内存中同一个“100”。
细想一下,其实Python这么做是对的,因为虽然外层是一个列表,但内层是不可变的对象,不可变对象为什么还要复制多份放内存不同地方呢?c和d外层列表的id和a不一样,但内层的子对象因为都是不可变对象,以后如果需要修改,反正原地修改无效,肯定要重新创建子对象,所以内层不可变子对象存一份即可。
尤其是子对象元素很多的时候,比如: a = [i for i in range(10000000)] b = a 如果Python不这么做,为了创建b得消耗多少内存呀?
那为啥d = copy.deepcopy(a)后,id[d[0])也和id(a[0])一样呢?不是说深拷贝是一点关系没有吗? 初学者可能认为深拷贝后的对象和原对象一点关系没有,完全是两个毫无关系的对象,那就too naive了。深拷贝和赋值相比,虽然拷贝前后的两个对象使用起来没有没有任何关系,但当在一个可变类型变量内部存在不可变类型子对象时,即使是深拷贝, Python不会重复再另存一份相同的不可变子对象,那样的话就太傻太天真了。(这块不理解说明你对可变类型和不可变类型还不算明白!)
情况1:= 赋值后的修改测试
b[0] = 0 print(b) print(a) print(id(a[0]) == id(b[0]))运行结果: [0, 200, 300] [0, 200, 300] True
赋值 = 操作后,修改b的子对象后,b[0]指向了内存中的存放数据“0”的新地址,然后a[0]的指向也发生变化,指向同一个“0”位置,a[0]的值也发生变化。由于a[0]和b[0]的指向同步修改,id值也始终如一。
情况2:浅拷贝后的修改测试
a = [100,200,300] c = copy.copy(a) c[0] = 10 print(c) print(a) print(id(a[0]) == c[0])运行结果: [10, 200, 300] [100, 200, 300] False
情况3:深拷贝后的修改测试:
import copy a = [100,200,300] c = copy.deepcopy(a) c[0] = 10 print(c) print(a) print(id(a[0]) == c[0])运行结果: [10, 200, 300] [100, 200, 300] False
小结:深拷贝后一个变化另外一个不变,与浅拷贝一致
情况1:= 赋值后的修改测试
a = [[1,2,3],[4,5,6]] b = a print(id(a) == id(b))结果为:True
a[0].append(100) print(a) print(b)结果为: [[1, 2, 3, 100], [4, 5, 6]] [[1, 2, 3, 100], [4, 5, 6]]
小结:Python中的 “=“ 赋值除非对不可变类型变量操作,其它所有情况一律是一个变,另外一个跟着变。
情况2:浅拷贝后的修改测试
a = [[1,2,3],[4,5,6]] b = copy.copy(a) print(id(a) == id(b)) print(id(a[0]) == id(b[0]))结果为: False True
a = [[1,2,3],[4,5,6]] b = copy.copy(a) b[0].append('c') print(a)结果为:[[1, 2, 3, ‘c’], [4, 5, 6]]
小节:当一个可变对象a中包含了可变类型的子对象时,用b = copy.copy(a) 浅拷贝后,虽然a和b的id不同,但a和b中的子对象的id是一样的,这样a和b就不是完全独立的,当对a或b进行成员的append和remove操作或把子对象换成不可变类型时,两者不相互影响。但进行对子对象原地修改时,一个的改变会影响另一个。
在python列表部分,可以用b = a[:] 这种切片操作进行复制,此时是赋值还是浅拷贝还是深拷贝呢?测试如下:
a = [[100],[200],[300]] b = a[:] print(id(a) == id(b)) print(id(a[0]) == id(b[0]))运行结果: False True 结果说明[:] 这种列表切片操作是“浅拷贝”。修改一下列表内部的子对象,看看另一个对象是否跟着变化
a[0].append(200) print(b)运行结果: [[100, 200], [200], [300]]
情况3:深拷贝后的修改测试: 可变类型对象包含复杂子对象,当用copy.deepcopy时,不管该对象包含的子对象是可变还是不可变的,最终深拷贝后的两个对象均复制独立,无任何相互影响。
a = [[1,2,3],[4,5,6]] b = copy.deepcopy(a) b[0].append('c') print(a) print(b)运行结果: [[1, 2, 3], [4, 5, 6]] [[1, 2, 3, ‘c’], [4, 5, 6]]
那为什么深拷贝能做到这一点呢,测试如下:
a = [[1,2,3],[4,5,6]] print(id(a[0]) == id(b[0]))运行结果: False 可以看出,deepcopy比较绝,内层子对象的id直接就是不一样的,这样今后对子对象咋修改都和原对象没一毛钱关系了。
(1)直接赋值,除非不可变类型,其它情况一律一个变,另外一个也变。 (2)浅拷贝,除非对可变类型内部的可变子对象进行修改操作外,一律一个变,另一个不变。 (3)深拷贝,使用效果看,一律都不相互影响。
感悟:Python中的赋值因为一律都是“指针”或“引用”的赋值,所以比较“霸道”,这种“霸道”的含义是赋值前后的两个变量除非是不可变类型,否则一律是一个变另外一个跟着变。这种“霸道”体现了Python对资源的“吝啬”,但有时也给编程带来一些麻烦,比如一个列表a,我现在就想对a进行一些增删改,但又不希望破坏a原本的值,这时候就需要Pyhton把a中的值克隆出来一份给列表b,我现在对b的任何操作,a均不受影响。此时就需要引入Copy库或对a进行列表切片后再赋值给b来完成这个愿望。
初学者往往理解不深或受其它语言的影响,可能把浅拷贝和深拷贝对立来看,其实两者是哥们关系,它们的出现都是针对赋值的“霸道”。浅拷贝和深拷贝其实是想把拷贝前后的两个变量之间脱离开,一个变另外一个不变,这是它们哥俩的“初心”。这时有深拷贝就行了,但为啥还有有它弟弟“浅拷贝”呢?估计设计者可能出于节省资源的目的,当在一个可变类型变量内部含有子对象的时候,做了一下折中的变化,使浅拷贝后外层的id不一样,但内层的id一致。
额外注意: a = b 一对一直接赋值后,如果对其中一个做了某些特殊的修改,或使用类似 += 赋值时要注意以下情况:
#a虽然是可变类型,但赋值给b后对a做修改,b不随之变换的几种特例: a = [1,2,3,4] c = [4,5,6,7] b = a a = a + c print(a) #打印[1, 2, 3, 4, 4, 5, 6, 7] print(b) #打印[1, 2, 3, 4] #上例如果改为+=赋值,则b会随之变化,+= 等价调用了extend方法 a = [1,2,3,4] c = [4,5,6,7] b = a a += c print(b) #[1, 2, 3, 4, 4, 5, 6, 7] #再看下面的对比例子 num = [1,2,3,4] def numf(num): num = num + num numf(num) print(num) #打印[1, 2, 3, 4] num = [1,2,3,4] def numf(num): num += num numf(num) print(num) #打印[1, 2, 3, 4, 1, 2, 3, 4] #集合的一些操作 a = {1,2,3,4} c = {3,4,5,6} b = a a = a | c #合并两个集合 print(a) #打印{1, 2, 3, 4, 5, 6} print(b) #打印{1, 2, 3, 4}练习题
#(1)试写出下列程序的运行结果: a = [[1,2,3],[4,5,6]] d = a del d[0] print(a) #(2)试写出下列程序的运行结果: a = '哈尔滨商业大学' d = a d = d + 'Harbin University of Commerce' print(a) #(3)试写出下列程序的运行结果: import copy a = [1,2,3,4,5] b = copy.copy(a) b.append(6) print(a) #(4)试写出下列程序的运行结果: a = [123,'hrb',[1,2,3]] b = copy.copy(a) a.append([4,5,6]) print(b) #(5)试写出下列程序的运行结果: a = [123,'hrb',[1,2,3]] b = copy.copy(a) a[2].append(4) print(b) #(6)试写出下列程序的运行结果: a = [123,'hrb',[1,2,3]] b = copy.deepcopy(a) a[2].append(4) print(b) #(7)试写出下列程序的运行结果: a = [123,'hrb',[1,2,3]] b = copy.copy(a) b[2] = 'hello' print(a) #(8)试写出下列程序的运行结果: a = [123,'hrb',[1,2,3]] b = copy.copy(a) print(id(a[0]) == id(b[0])) #(9)试写出下列程序的运行结果: a = [123,'hrb',[1,2,3]] b = copy.deepcopy(a) print(id(a[0]) == id(b[0])) #(10)试写出下列程序的运行结果: a = [123,'hrb',[1,2,3]] b = copy.deepcopy(a) print(id(a[2]) == id(b[2]))参考答案 (1)[[4, 5, 6]] (2)哈尔滨商业大学 (3) [1, 2, 3, 4, 5] (4)[123, ‘hrb’, [1, 2, 3]] (5)[123, ‘hrb’, [1, 2, 3, 4]] (6)[123, ‘hrb’, [1, 2, 3]] (7)[123, ‘hrb’, [1, 2, 3]] (8)True (9)True (10)False