1. 可变对象和不可变对象
为了更好地理解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}')
2. 赋值操作、深拷贝与浅拷贝
在Python中,赋值操作、深拷贝以及浅拷贝在可变类型与不可变类型值的表现是不一样的
-
2.1 不可变类型中的情况
在不可变类型中,比较简单,三种操作都不会开辟新的内存空间:
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的值时,会新开辟内存来存储新值,并将变量的指向更新到新内存。
-
2.2 可变类型中的情况
在可变类型中,赋值操作不会开辟新的内存空间,而浅拷贝和深拷贝会开辟新的内存,所以对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]。