拷贝相关
父对象、子对象以及直接赋值、浅拷贝(copy)、深拷贝(deepcopy)-CSDN博客
a = 2
b = a
a = 3
内存的存储方式:
- 先再内存中开栈存储
2
这个数据的空间 a
指向数据为2
的内存空间 地址b = a
此时b
指向2
的内存空间的 地址a = 3
内存中开栈存储3
这个数据的空间- 此时
a = 3
中,a
重新指向数据为3
的内存地址 - 问:此时
b
的值是多少?
总结:Python 是动态语言,变量只是对象的引用,不同于 C 中将值赋给变量。动态语言中,变量只是对象的引用,即对象本身有自己的内存空间,变量只是指向了该内存空间(所有的变量都类似指针)。
直接赋值
(变量、列表、字典、元组、集合等)直接赋值都只是原来对象的引用,赋值后改变原来对象,新对象也会 可能发生改变,具体情况如下:
变量:重新赋值
a = 3 # a 指向数据 3 的内存空间
b = a # b 也指向数据 3 的内存空间
a = 4 # a 指向数据 4 的内存空间,b 指向的空间不变,b = 3
这里我们发现,赋值就等于 开辟了新的内存空间。
列表:重新赋值
list_1 = [1, 2, 3]
list_2 = list_1
list_1 = [4, 5, 6]
print(list_1) # [4, 5, 6]
print(list_2) # [1, 2, 3]
在列表的直接赋值中,list_1
由 [1, 2, 3]
变为 [4, 5, 6]
,相当于 重新开辟 了一个 [4, 5, 6]
对象的内存空间,并将 list_1
指向了该空间。之前指定的list_2
的指向内存空间没有发生改变,依旧指向 [1, 2, 3]
,所以 list_2
没有发生变化。
列表:插入新值
list_1 = [1, 2, 3]
list_2 = list_1
list_1.append(4)
print(list_1) # [1, 2, 3, 4]
print(list_2) # [1, 2, 3, 4]
在列表的插入新值中,由于通过 append(4)
方法在尾部插入新值,[1, 2, 3]
内存空间扩充,但 list_1
指向并 没有发生变化,同时该过程中,list_2
也依旧指向该空间,所以 list_2
一同发生变化。
因此,我们接下来就看重新赋值和原地修改对不同数据结构的影响。
元组
tup_1 = (1, 2, 3, 4, 5)
tup_2 = tup_1
tup_1 = tup_1 + (6, 7, 8, 9, 10) # tup_1 = (6, 7, 8, 9, 19)
print(tup_1) # (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
print(tup_2) # (1, 2, 3, 4, 5)
元组和列表不同,元组只要确定后,无论是 重新赋值 还是 插入新值,tup_2
都不会发生变化。
字典
dict_1 = { "a": 11,"b": 22, "c": 33, "d": 44 }
dict_2 = dict_1
dict_1.popitem() # dict_1["a"] = 10
print(dict_1) # {'a': 11, 'b': 22, 'c': 33}
print(dict_2) # {'a': 11, 'b': 22, 'c': 33}
dict_1.popitem()
是对原来的字典 内存空间直接操作,dict_1
、dict_2
都指向该空间,所以都会发生变换。因此无论是修改还是插入、删除这些在原内存空间上的操作,内存空间上的值都发生了变化。
当然,如果对 dict_1
重新赋值,那么 dict_2
的值是不会变的。
集合
set_1 = {1 ,2, 3, 4, 5}
set_2 = set_1
set_1.add(6) # set_2 = cp.copy(set_1)
print(set_1) # {1, 2, 3, 4, 5, 6}
print(set_2) # {1, 2, 3, 4, 5, 6}
直接赋值同样也会修改 set_2
的值,因为修改了内存空间上的值。当然,如果对 set_1
重新赋值,那么 set_2
的值是不会变的。但我们注意到,copy.copy(set_1)
后,即使 set_1
修改了(不是重新赋值),set_2
依然不会变。
总结
- 所有重新赋值都不会修改
b
,因此对a
重新赋值等价于 开辟新的内存空间 - 对
a
的修改会影响 变量、列表、集合、字典 - 不会影响 元组
浅拷贝与深拷贝
- 浅拷贝
copy()
:拷贝父对象,不会拷贝父对象内部的子对象 - 深拷贝
deepcopy()
:完全拷贝了父对象及其子对象
举例:
import copy as cp
a = [1, 2, 3, 4, ['a', 'b']]
b = a # 赋值。传对象的引用
c = a[:] # 利用分片操作进行拷贝(浅拷贝)
d = cp.copy(a) # 对象拷贝,浅拷贝
e = cp.deepcopy(a) # 对象拷贝,深拷贝
a.append(5) # 改动对象 a
a[4].append('c') # 改动对象 a 中的 ['a', 'b'] 列表子对象
print(a) # [1, 2, 3, 4, ['a', 'b', 'c'], 5]
print(b) # [1, 2, 3, 4, ['a', 'b', 'c'], 5]
print(c) # [1, 2, 3, 4, ['a', 'b', 'c']]
print(d) # [1, 2, 3, 4, ['a', 'b', 'c']]
print(e) # [1, 2, 3, 4, ['a', 'b']]
其实,浅拷贝,深拷贝底层还是 变量对内存空间指向层级 的不同。
直接复制:引用
数据都指向最顶层的内存空间:
a = [1, 2, 3, 4, ['a', 'b']]
b = a
浅拷贝
指向父对象的内存空间:
a = [1, 2, 3, 4, ['a', 'b']]
b = copy.copy(a) # b = a[:] 分片操作
深拷贝
指向父对象及其所有子对象内存空间:
a = [1, 2, 3, 4, ['a', 'b']]
b = copy.deepcopy(a)