首先分享Python里面的数据类型
1、不可变类型:Number(数字)、String(字符串)、Tuple(元组)。
不可变数据类型在第一次声明赋值的时候, 会在内存中开辟一块空间, 用来存放这个变量被赋的值, 而这个变量实际上存储的, 并不是被赋予的这个值, 而是存放这个值所在空间的内存地址, 通过这个地址, 变量就可以在内存中取出数据了. 所谓不可变就是说, 我们不能改变这个数据在内存中的值, 所以当我们改变这个变量的赋值时, 只是在内存中重新开辟了一块空间, 将这一条新的数据存放在这一个新的内存地址里, 而原来的那个变量就不在引用原数据的内存地址而转为引用新数据的内存地址了.
如图:
In [11]: a = 1 # int为不可变类型,赋值 In [12]: id(a) # 查看a地址 Out[12]: 4541615920 In [13]: id(1) # 查看数字1的地址 Out[13]: 4541615920 In [14]: a = 2 # 为a重新赋值 In [15]: id(a) # 查看a地址,指向新的内存地址 Out[15]: 4541615952 In [16]: id(2) Out[16]: 4541615952 In [17]: id(1) Out[17]: 4541615920
In [28]: a = "hello" # 为a赋值 hello In [29]: b = a # 将a赋值给a In [30]: id(a) Out[30]: 4572905712 In [31]: id(b) # 通过查看a和b的地址,说明a和b指向同一个地址 Out[31]: 4572905712 In [32]: b = "hi" # 为b赋值 hi In [33]: a # 查看a未被修改 Out[33]: 'hello' In [34]: id(a) # 查看a地址未发生修改 Out[34]: 4572905712 In [35]: id(b) # 查看b指向新的地址,说明b=a时,是将a的引用指向b Out[35]: 4544178736
b=a直接赋值时,默认是浅拷贝传递对象的引用而已,原始数据若为不可变类型时,被赋值的b改变时,相当于在内存中重新开辟了一块空间, 将这一条新的数据存放在这一个新的内存地址里, 而原来的那个变量就不在引用原数据的内存地址而转为引用新数据的内存地址了.
2、可变类型:Set(集合)、List(列表)、Dictionary(字典)。
当数据类型对应的变量的值发生改变时,它对应的内存地址也会发生改变,即当 e = [1,2] 时,是e指向列表[1,2]对象,而不是将e指向列表[1,2]的地址
如图:
In [10]: e = [1,2] In [11]: id(e) Out[11]: 4408548080 In [12]: id([1,2]) # e指向[1,2]对象,而不是[1,2]的地址 Out[12]: 4408714656
In [10]: e = [1,2] # 给变量e赋值可变类型列表[1,2] In [11]: id(e) # 查看e地址 Out[11]: 4408548080 In [12]: id([1,2]) # 查看列表[1,2]地址 Out[12]: 4408714656 In [13]: f = e # 将e赋值给f,两者共享同一个列表[1,2]对象 In [14]: id(f) #查看f地址,其实是将c指向于e的引用地址,而非列表[1,2]的实际地址 Out[14]: 4408548080 In [15]: e[0] = 11 # 通过e修改列表内元素 In [16]: e Out[16]: [11, 2] In [17]: f # 查看f中对应元素同样被修改,验证e和f指向一个引用对象,说明f=e时,是将e的对象的地址指向f Out[17]: [11, 2]
f = e直接赋值时,默认是浅拷贝传递对象的引用而已,原始数据e若为可变类型发生变化,被赋值的f也会做相同的改变。
3. 浅拷贝
浅拷贝是对于一个对象的顶层拷贝
通俗的理解是:拷贝了引用,并没有拷贝内容
In [36]: a = [1,2] In [37]: b = a In [38]: id(a) Out[38]: 4573291056 In [39]: id(b) # 浅拷贝,a和b地址相同,说明b=a是对a引用的拷贝 Out[39]: 4573291056
通过上面的结果,说明当给一个变量赋值时。其实就是讲数据的引用复制了一份给另外一个变量 ,就是属于简单的浅拷贝
In [36]: a = [1,2] In [37]: b = a In [38]: id(a) Out[38]: 4573291056 In [39]: id(b) Out[39]: 4573291056 In [40]: a[0] = 11 In [41]: a # a中元素被修改 Out[41]: [11, 2] In [42]: b # b中元素被修改 Out[42]: [11, 2]
通过上面的结果,说明通过任何一个引用修改可变变量之后,另外一个变量的值也发生了变化。
In [24]: g = [2,3] In [25]: h = copy.copy(g) # 浅拷贝 In [26]: id(g) Out[26]: 4409269536 In [27]: id(h) Out[27]: 4408716176 In [28]: id([2,3]) Out[28]: 4408685824 In [30]: id(2) # 获取2的地址 Out[30]: 4378861392 In [31]: id(g[0]) # 获取g[0]位置的地址 Out[31]: 4378861392 In [32]: id(h[0]) # 获取h[0]位置的地址 Out[32]: 4378861392
通过上面的结果,说明浅拷贝只是对外层引用的拷贝,内层地址一样。
In [33]: a = [11,22] In [34]: b = [33,44] In [35]: c = [a,b] In [36]: id(a) Out[36]: 4408713536 In [37]: id(b) Out[37]: 4408547680 In [38]: id(c) Out[38]: 4408615712 In [39]: d = copy.copy(c) # 对c进行浅拷贝,得到d In [40]: id(d) # 查看d地址 Out[40]: 4409421248 In [41]: id(d[0]) # 查看d[0]位置元素地址,与a地址相同,说明只进行外层拷贝 Out[41]: 4408713536 In [42]: id(d[1]) # 查看d[1]位置元素地址,与b地址相同,说明只进行外层拷贝 Out[42]: 4408547680 In [43]: a.append(55) # 给a列表添加元素 In [44]: c # 查看c中元素也发生变化 Out[44]: [[11, 22, 55], [33, 44]] In [45]: d # 查看d中元素也发生变化,说明是对外层的拷贝 Out[45]: [[11, 22, 55], [33, 44]] In [46]: d.append(66) # 给d中添加元素 In [47]: d # 查看d中元素发生变化 Out[47]: [[11, 22, 55], [33, 44], 66] In [48]: c # 查看c中元素未发生变化,说明d未外层拷贝 Out[48]: [[11, 22, 55], [33, 44]]
通过上面的结果,说明浅拷贝只是对外层引用的拷贝,内层地址一样,修改外层,拷贝的新对象不会发生变化,修改内层元素,拷贝的新对象会发生变化。
4. 深拷贝
深拷贝是对于一个对象所有层次的拷贝(递归), 所以原始对象的改变不会造成深拷贝里任何子元素的改变
In [61]: a = [11,22] In [62]: b = [33,44] In [63]: c = [a,b] In [64]: c Out[64]: [[11, 22], [33, 44]] In [65]: d = copy.deepcopy(c) # 深拷贝得到d In [66]: d Out[66]: [[11, 22], [33, 44]] In [67]: f = copy.copy(c) # 浅拷贝得到f In [68]: f Out[68]: [[11, 22], [33, 44]] In [69]: a.append(55) # a中添加元素55 In [70]: d # 深拷贝的d中没有修改,说明是对内层也进行copy Out[70]: [[11, 22], [33, 44]] In [71]: f # 浅拷贝的f中发生变化,说明是只对外层进行了copy Out[71]: [[11, 22, 55], [33, 44]]