为了更好地理解Python中的深拷贝、浅拷贝以及赋值操作的区别,首先得熟悉Python中的对象类型。Python中的类型可以分为可变类型和不可变类型两种:
不可变对象
不可变对象的值一旦定义就不能更改,若强行改变不可变对象的值,由于其值是不可变的,则会重新分配一块内存,将新的值存入这块新的内存中,再将变量指向新的内存地址。常见的不可变类型包括元组、数值类型、字符串等
a = 1
id1 = id(a)
a = 2 # 改变不可变对象a的值
id2 = id(a)
print(id1 == id2)
可变对象
与不可变对象相反,可变对象的值可以直接修改而不改变其在内存中的地址,即可以进行原地修改,可变类型包括字典、列表、自定义类等
a = [1, 2, 3]
id1 = id(a)
a[0] = 1
id2 = id(a)
print(id1 == id2)
复杂对象
对于有可变对象和不可变对象嵌套而来的复杂对象而言,仍然遵循上述的规律:
a = [1, 2, [3, 4]]
id1 = [id(_) for _ in a]
print(f'id1: {id1}')
a[0] = 2
a[2].append(5)
id2 = [id(_) for _ in a]
print(f'id2: {id2}')
在Python中,赋值操作、深拷贝以及浅拷贝在可变类型与不可变类型值的表现是不一样的
在不可变类型中,比较简单,三种操作都不会开辟新的内存空间:
import copy
a = 1
b = a # 赋值操作
c = copy.copy(a) # 浅拷贝
d = copy.deepcopy(a) # 深拷贝
print(f'id(a) equals id(b)? {id(a) == id(b)}')
print(f'id(a) equals id(c)? {id(a) == id(c)}')
print(f'id(a) equals id(d)? {id(a) == id(d)}')
a = 2
print(f'a is {a}')
print(f'b is {b}')
print(f'c is {c}')
print(f'd is {d}')
可以看出,变量a, b, c, d的地址都是相同的,但是由于不可变类型的特殊性质,虽然四个变量具有相同的地址,但是当改变变量a的值时,会新开辟内存来存储新值,并将变量的指向更新到新内存。
在可变类型中,赋值操作不会开辟新的内存空间,而浅拷贝和深拷贝会开辟新的内存,所以对a的改变不会影响到c和d:
import copy
a = [1, 2, 3]
b = a # 赋值操作
c = copy.copy(a) # 浅拷贝
d = copy.deepcopy(a) # 深拷贝
print(f'id(a) equals id(b)? {id(a) == id(b)}')
print(f'id(a) equals id(c)? {id(a) == id(c)}')
print(f'id(a) equals id(d)? {id(a) == id(d)}')
a.append(4)
print(f'a is {a}')
print(f'b is {b}')
print(f'c is {c}')
print(f'd is {d}')
那么,浅拷贝和深拷贝有什么区别呢,在上述的单一的可变类型这种简单的情况下,似乎看不出来两种拷贝方式的区别,但在下述的复杂类型中就可以看出他们的区别了。
复杂类型中的情况
在复杂类型中(指基础类型的嵌套),浅拷贝和深拷贝遵循以下规律:
import copy
a = [1, 2, [3, 4]]
b = a # 赋值操作
c = copy.copy(a) # 浅拷贝
d = copy.deepcopy(a) # 深拷贝
print(f'id(a) equals id(b)? {id(a) == id(b)}')
print(f'id(a) equals id(c)? {id(a) == id(c)}')
print(f'id(a) equals id(d)? {id(a) == id(d)}')
a_item_id = [id(_) for _ in a]
b_item_id = [id(_) for _ in b]
c_item_id = [id(_) for _ in c]
d_item_id = [id(_) for _ in d]
print(f'a_item_id: {a_item_id}')
print(f'b_item_id: {b_item_id}')
print(f'c_item_id: {c_item_id}')
print(f'd_item_id: {d_item_id}')
a.append(4)
a[2].append(100)
print(f'a is {a}')
print(f'b is {b}')
print(f'c is {c}')
print(f'd is {d}')
如上结果所示,c[2]的内存地址和a[2]相同,说明浅拷贝不会为内存的数据开辟新的内存空间,而d[2]的内存地址和a[2]不同,正好说明了深拷贝会为内层的数据开辟新的内存,故a[2]的改变会影响到b[2]和c[2],不会影响到d[2]。